[jackrabbit] 08/21: Imported Upstream version 2.10.1

Markus Koschany apo-guest at moszumanska.debian.org
Sat Jun 20 22:06:16 UTC 2015


This is an automated email from the git hooks/post-receive script.

apo-guest pushed a commit to branch master
in repository jackrabbit.

commit c138f747c32fd97a3fb3720833bc3ce613094a23
Author: Markus Koschany <apo at gambaru.de>
Date:   Sat Jun 20 21:04:25 2015 +0200

    Imported Upstream version 2.10.1
---
 NOTICE.txt                                         |    2 +-
 README.txt                                         |    6 +-
 RELEASE-NOTES.txt                                  |  192 +--
 check-release.sh                                   |  115 --
 examples/jackrabbit-firsthops/pom.xml              |   20 +-
 .../org/apache/jackrabbit/firsthops/FirstHop.java  |    7 +-
 .../org/apache/jackrabbit/firsthops/SecondHop.java |    9 +-
 .../org/apache/jackrabbit/firsthops/ThirdHop.java  |    9 +-
 jackrabbit-api/pom.xml                             |    4 +-
 .../jackrabbit/api/JackrabbitRepository.java       |   53 +
 .../org/apache/jackrabbit/api/ReferenceBinary.java |   39 +
 .../jackrabbit/api/ReferenceBinaryException.java   |   27 +
 .../jackrabbit/api/jmx/EventListenerMBean.java     |  110 ++
 .../apache/jackrabbit/api/jmx/package-info.java    |    2 +-
 .../api/management/DataStoreGarbageCollector.java  |    8 +-
 .../jackrabbit/api/management/package-info.java    |    2 +-
 .../api/observation/JackrabbitEvent.java           |   70 +-
 .../api/observation/JackrabbitEventFilter.java     |  309 +++++
 .../observation/JackrabbitObservationManager.java  |   55 +
 .../jackrabbit/api/observation/package-info.java   |    2 +-
 .../api/query/JackrabbitQueryResult.java           |   38 +
 .../api/security/JackrabbitAccessControlEntry.java |   29 +-
 .../api/security/JackrabbitAccessControlList.java  |   46 +-
 .../api/security/principal/PrincipalManager.java   |   10 +-
 .../jackrabbit/api/security/user/Authorizable.java |   12 +-
 .../security/user/AuthorizableTypeException.java   |   29 +
 .../apache/jackrabbit/api/security/user/User.java  |   11 +-
 .../jackrabbit/api/security/user/UserManager.java  |   81 +-
 .../jackrabbit/api/security/user/package-info.java |    2 +-
 .../jackrabbit/api/stats/RepositoryStatistics.java |   87 +-
 jackrabbit-aws-ext/README.txt                      |   28 +
 jackrabbit-aws-ext/pom.xml                         |  109 ++
 .../org/apache/jackrabbit/aws/ext/S3Constants.java |  107 ++
 .../jackrabbit/aws/ext/S3RequestDecorator.java     |   87 ++
 .../java/org/apache/jackrabbit/aws/ext/Utils.java  |  188 +++
 .../apache/jackrabbit/aws/ext/ds/S3Backend.java    |  912 +++++++++++++
 .../apache/jackrabbit/aws/ext/ds/S3DataStore.java  |   50 +
 .../org/apache/jackrabbit/aws/ext/TestAll.java     |   59 +
 .../jackrabbit/aws/ext/ds/S3TestDataStore.java     |   47 +
 .../jackrabbit/aws/ext/ds/TestS3DSAsyncTouch.java  |   53 +
 .../jackrabbit/aws/ext/ds/TestS3DSWithSSES3.java   |   94 ++
 .../aws/ext/ds/TestS3DSWithSmallCache.java         |   53 +
 .../org/apache/jackrabbit/aws/ext/ds/TestS3Ds.java |  144 ++
 .../jackrabbit/aws/ext/ds/TestS3DsCacheOff.java    |   50 +
 .../src/test/resources/aws.properties              |   45 +
 .../src/test/resources/log4j.properties            |   31 +
 .../src/test/resources/repository_sample.xml       |  170 +++
 jackrabbit-bundle/pom.xml                          |    6 +-
 jackrabbit-core/assembly.xml                       |   30 -
 jackrabbit-core/pom.xml                            |   68 +-
 .../jackrabbit/core/BatchedItemOperations.java     |   60 +-
 .../jackrabbit/core/CachingHierarchyManager.java   |   58 +-
 .../jackrabbit/core/DefaultSecurityManager.java    |   40 +-
 .../apache/jackrabbit/core/HierarchyManager.java   |    8 +-
 .../jackrabbit/core/HierarchyManagerImpl.java      |   60 +-
 .../apache/jackrabbit/core/InternalXAResource.java |   70 -
 .../jackrabbit/core/ItemLifeCycleListener.java     |    4 +-
 .../org/apache/jackrabbit/core/ItemManager.java    |   13 +-
 .../apache/jackrabbit/core/ItemSaveOperation.java  |   11 +-
 .../jackrabbit/core/JackrabbitRepositoryStub.java  |    6 +-
 .../apache/jackrabbit/core/LazyItemIterator.java   |    6 +-
 .../java/org/apache/jackrabbit/core/NodeImpl.java  |  108 +-
 .../jackrabbit/core/ProtectedItemModifier.java     |   14 +-
 .../apache/jackrabbit/core/RepositoryChecker.java  |   93 +-
 .../apache/jackrabbit/core/RepositoryContext.java  |   47 +-
 .../org/apache/jackrabbit/core/RepositoryImpl.java |   78 +-
 .../org/apache/jackrabbit/core/SessionImpl.java    |    6 +-
 .../jackrabbit/core/SessionMoveOperation.java      |    8 +-
 .../org/apache/jackrabbit/core/SystemSession.java  |   23 +-
 .../apache/jackrabbit/core/TransactionContext.java |  346 -----
 .../jackrabbit/core/TransactionException.java      |   44 -
 .../core/UserPerWorkspaceSecurityManager.java      |   29 +-
 .../apache/jackrabbit/core/VersionManagerImpl.java |    2 +-
 .../org/apache/jackrabbit/core/WorkspaceImpl.java  |   12 +-
 .../org/apache/jackrabbit/core/XASessionImpl.java  |   14 +-
 .../jackrabbit/core/ZombieHierarchyManager.java    |   12 +-
 .../apache/jackrabbit/core/cache/CacheManager.java |    7 +-
 .../jackrabbit/core/cluster/ClusterNode.java       |  121 +-
 .../core/cluster/UpdateEventChannel.java           |    6 +-
 .../core/cluster/WorkspaceEventChannel.java        |   60 +-
 .../jackrabbit/core/cluster/WorkspaceListener.java |   76 +-
 .../jackrabbit/core/cluster/WorkspaceRecord.java   |  412 +++---
 .../apache/jackrabbit/core/config/BeanConfig.java  |   88 +-
 .../jackrabbit/core/config/BeanConfigVisitor.java  |   25 +
 .../apache/jackrabbit/core/config/BeanFactory.java |   22 +
 .../jackrabbit/core/config/ClusterConfig.java      |   28 +-
 .../core/config/ConfigurationEntityResolver.java   |   17 +
 .../core/config/ConfigurationParser.java           |    8 +-
 .../jackrabbit/core/config/NoOpConfigVisitor.java  |   24 +
 .../jackrabbit/core/config/RepositoryConfig.java   |    4 +-
 .../core/config/RepositoryConfigurationParser.java |  162 ++-
 .../jackrabbit/core/config/SimpleBeanFactory.java  |   57 +
 .../jackrabbit/core/config/WorkspaceConfig.java    |   40 +-
 .../jackrabbit/core/data/AbstractDataRecord.java   |   82 --
 .../jackrabbit/core/data/DataIdentifier.java       |  100 --
 .../apache/jackrabbit/core/data/DataRecord.java    |   55 -
 .../org/apache/jackrabbit/core/data/DataStore.java |  140 --
 .../jackrabbit/core/data/DataStoreFactory.java     |   42 -
 .../jackrabbit/core/data/FileDataRecord.java       |   69 -
 .../apache/jackrabbit/core/data/FileDataStore.java |  459 -------
 .../jackrabbit/core/data/GarbageCollector.java     |  436 ------
 .../jackrabbit/core/data/LazyFileInputStream.java  |  167 ---
 .../jackrabbit/core/data/db/DbDataRecord.java      |   71 -
 .../jackrabbit/core/data/db/DbDataStore.java       |  993 --------------
 .../core/data/db/TempFileInputStream.java          |  134 --
 .../apache/jackrabbit/core/fs/BasedFileSystem.java |  186 ---
 .../jackrabbit/core/fs/FileSystemFactory.java      |   39 -
 .../jackrabbit/core/fs/FileSystemPathUtil.java     |  228 ----
 .../jackrabbit/core/fs/FileSystemResource.java     |  225 ----
 .../jackrabbit/core/fs/db/DB2FileSystem.java       |    6 +-
 .../apache/jackrabbit/core/fs/db/DbFileSystem.java |    4 +-
 .../jackrabbit/core/fs/db/DerbyFileSystem.java     |    4 +-
 .../jackrabbit/core/fs/db/MSSqlFileSystem.java     |    4 +-
 .../jackrabbit/core/fs/db/OracleFileSystem.java    |    8 +-
 .../jackrabbit/core/fs/local/LocalFileSystem.java  |  386 ------
 .../jackrabbit/core/gc/GarbageCollector.java       |  511 +++++++
 .../java/org/apache/jackrabbit/core/id/NodeId.java |   28 +-
 .../jackrabbit/core/id/SeededSecureRandom.java     |  221 ++--
 .../jackrabbit/core/jmx/QueryStatManager.java      |  139 --
 .../jackrabbit/core/jndi/BindableRepository.java   |    6 +-
 .../jackrabbit/core/journal/AbstractJournal.java   |  125 +-
 .../jackrabbit/core/journal/AppendRecord.java      |   15 +-
 .../jackrabbit/core/journal/DatabaseJournal.java   |   37 +-
 .../jackrabbit/core/journal/FileJournal.java       |    2 +-
 .../jackrabbit/core/journal/FileRevision.java      |   21 +-
 .../apache/jackrabbit/core/journal/Journal.java    |    7 +-
 .../core/journal/OracleDatabaseJournal.java        |    2 +-
 .../apache/jackrabbit/core/journal/ReadRecord.java |    2 +-
 .../org/apache/jackrabbit/core/journal/Record.java |    3 +-
 .../org/apache/jackrabbit/core/lock/LockImpl.java  |   14 +-
 .../jackrabbit/core/lock/LockManagerImpl.java      |  288 ++--
 .../apache/jackrabbit/core/lock/XAEnvironment.java |    4 +-
 .../apache/jackrabbit/core/lock/XALockImpl.java    |    2 +-
 .../apache/jackrabbit/core/lock/XALockManager.java |   10 +-
 .../core/nodetype/BitSetENTCacheImpl.java          |    8 +-
 .../core/nodetype/EffectiveNodeType.java           |    8 +-
 .../core/nodetype/EffectiveNodeTypeCache.java      |    2 +-
 .../core/nodetype/EffectiveNodeTypeCacheImpl.java  |    8 +-
 .../core/nodetype/NodeTypeDefinitionImpl.java      |    6 +-
 .../jackrabbit/core/nodetype/NodeTypeImpl.java     |    2 +-
 .../core/nodetype/NodeTypeManagerImpl.java         |   19 +-
 .../jackrabbit/core/nodetype/NodeTypeRegistry.java |  136 +-
 .../virtual/VirtualNodeTypeStateProvider.java      |    2 +-
 .../jackrabbit/core/observation/EventConsumer.java |    7 +-
 .../jackrabbit/core/observation/EventFilter.java   |   65 +-
 .../jackrabbit/core/observation/EventImpl.java     |    4 +-
 .../core/observation/EventJournalImpl.java         |    3 +-
 .../jackrabbit/core/observation/EventState.java    |   94 ++
 .../core/observation/EventStateCollection.java     |   26 +-
 .../core/observation/ObservationDispatcher.java    |   19 +-
 .../core/observation/ObservationManagerImpl.java   |   95 +-
 .../core/observation/SynchronousEventListener.java |    2 +-
 .../persistence/IterablePersistenceManager.java    |   34 +-
 .../jackrabbit/core/persistence/PMContext.java     |    2 +-
 .../bundle/AbstractBundlePersistenceManager.java   |   96 +-
 .../bundle/BundleFsPersistenceManager.java         |    5 +-
 .../bundle/ConsistencyCheckerError.java            |   78 ++
 .../persistence/bundle/ConsistencyCheckerImpl.java |  707 ++++++++++
 .../check/ConsistencyCheckListener.java            |   46 +
 .../core/persistence/check/ConsistencyChecker.java |   26 +-
 .../core/persistence/check/ReportItem.java         |   16 +-
 .../core/persistence/check/ReportItemImpl.java     |   18 +-
 .../persistence/db/DatabasePersistenceManager.java |   10 +-
 .../persistence/db/DerbyPersistenceManager.java    |    4 +-
 .../persistence/db/MSSqlPersistenceManager.java    |    4 +-
 .../persistence/db/OraclePersistenceManager.java   |   14 +-
 .../persistence/db/SimpleDbPersistenceManager.java |    4 +-
 .../mem/InMemBundlePersistenceManager.java         |   28 +-
 .../persistence/mem/InMemPersistenceManager.java   |    2 +-
 .../pool/BundleDbPersistenceManager.java           |  368 ++----
 .../core/persistence/pool/DbNameIndex.java         |    7 +-
 .../persistence/pool/DerbyPersistenceManager.java  |   26 +-
 .../persistence/pool/H2PersistenceManager.java     |    2 +-
 .../persistence/pool/MySqlPersistenceManager.java  |    2 +-
 .../core/persistence/pool/NGKDbNameIndex.java      |    2 +-
 .../persistence/pool/OraclePersistenceManager.java |    2 +-
 .../core/persistence/pool/PostgreSQLNameIndex.java |    3 +-
 .../pool/PostgreSQLPersistenceManager.java         |    2 +-
 .../core/persistence/util/BundleReader.java        |    6 +-
 .../core/persistence/util/BundleWriter.java        |   77 +-
 .../jackrabbit/core/persistence/util/NodeInfo.java |  189 +++
 .../core/persistence/util/NodePropBundle.java      |   29 +-
 .../core/query/OnWorkspaceInconsistency.java       |   32 +
 .../apache/jackrabbit/core/query/QueryImpl.java    |    2 +-
 .../core/query/QueryObjectModelImpl.java           |    2 +-
 .../core/query/lucene/AbstractExcerpt.java         |   29 +-
 .../core/query/lucene/AbstractIndex.java           |   75 +-
 .../core/query/lucene/AbstractQueryImpl.java       |    2 +-
 .../core/query/lucene/AbstractWeight.java          |    4 +-
 .../core/query/lucene/CachingIndexReader.java      |   14 +-
 .../core/query/lucene/CachingMultiIndexReader.java |   38 -
 .../core/query/lucene/CaseTermQuery.java           |    1 +
 .../core/query/lucene/ChildAxisQuery.java          |   11 +-
 .../core/query/lucene/ChildNodesQueryHits.java     |    3 +
 .../core/query/lucene/CommittableIndexReader.java  |   90 +-
 .../core/query/lucene/ConsistencyCheck.java        |  493 ++++++-
 .../core/query/lucene/ConsistencyCheckError.java   |   17 +-
 .../core/query/lucene/DefaultHighlighter.java      |    2 +-
 .../core/query/lucene/DefaultRedoLog.java          |    2 +-
 .../jackrabbit/core/query/lucene/DerefQuery.java   |    4 +-
 .../core/query/lucene/DescendantSelfAxisQuery.java |   31 +-
 .../query/lucene/FileBasedNamespaceMappings.java   |    2 +-
 .../core/query/lucene/FilterSearcher.java          |  100 ++
 .../jackrabbit/core/query/lucene/IDField.java      |    9 +-
 .../jackrabbit/core/query/lucene/IOCounters.java   |   13 +-
 .../core/query/lucene/IndexFormatVersion.java      |    4 +-
 .../jackrabbit/core/query/lucene/IndexHistory.java |   10 +-
 .../jackrabbit/core/query/lucene/IndexInfos.java   |   46 +-
 .../core/query/lucene/IndexMigration.java          |   42 +-
 .../query/lucene/IndexingConfigurationImpl.java    |   57 +-
 .../core/query/lucene/JackrabbitAnalyzer.java      |   79 +-
 .../core/query/lucene/JackrabbitQuery.java         |    2 +-
 .../core/query/lucene/JackrabbitQueryParser.java   |   10 +-
 .../core/query/lucene/JackrabbitTermQuery.java     |    6 +-
 .../core/query/lucene/LazyTextExtractorField.java  |   85 +-
 .../core/query/lucene/LuceneQueryBuilder.java      |    4 +-
 .../core/query/lucene/LuceneQueryFactory.java      |    9 +-
 .../core/query/lucene/LuceneQueryHits.java         |    2 +-
 .../core/query/lucene/MatchAllQuery.java           |    2 +-
 .../jackrabbit/core/query/lucene/MoreLikeThis.java |    7 +-
 .../jackrabbit/core/query/lucene/MultiIndex.java   |   26 +-
 .../jackrabbit/core/query/lucene/MultiScorer.java  |    2 +-
 .../jackrabbit/core/query/lucene/NodeIndexer.java  |  241 ++--
 .../core/query/lucene/NodeIteratorImpl.java        |    2 +-
 .../core/query/lucene/NormalizeSortComparator.java |   86 ++
 .../jackrabbit/core/query/lucene/NotQuery.java     |   11 +-
 .../core/query/lucene/ParentAxisQuery.java         |    4 +-
 .../core/query/lucene/PersistentIndex.java         |    2 +-
 .../core/query/lucene/PredicateDerefQuery.java     |    4 +-
 .../core/query/lucene/QueryResultImpl.java         |  116 +-
 .../jackrabbit/core/query/lucene/RangeQuery.java   |    8 +-
 .../core/query/lucene/ReadOnlyIndexReader.java     |   37 +-
 .../jackrabbit/core/query/lucene/Recovery.java     |    2 +-
 .../core/query/lucene/RefCountingIndexReader.java  |   14 +-
 .../core/query/lucene/RowIteratorImpl.java         |   14 +-
 .../jackrabbit/core/query/lucene/SearchIndex.java  |  193 ++-
 .../core/query/lucene/SharedFieldCache.java        |   35 +-
 .../apache/jackrabbit/core/query/lucene/Util.java  |   10 +-
 .../core/query/lucene/WildcardNameQuery.java       |    2 +-
 .../core/query/lucene/WildcardQuery.java           |   56 +-
 .../query/lucene/directory/FSDirectoryManager.java |   27 +-
 .../query/lucene/directory/IndexInputStream.java   |    2 +-
 .../query/lucene/directory/IndexOutputStream.java  |    4 +-
 .../query/lucene/hits/AbstractHitCollector.java    |    2 +-
 .../core/query/lucene/join/AbstractRow.java        |    5 +-
 .../core/query/lucene/join/AncestorNodeJoin.java   |    2 +-
 .../query/lucene/join/AncestorPathNodeJoin.java    |    2 +-
 .../core/query/lucene/join/ChildNodeJoin.java      |    2 +-
 .../core/query/lucene/join/DescendantNodeJoin.java |    2 +-
 .../query/lucene/join/DescendantPathNodeJoin.java  |    2 +-
 .../core/query/lucene/join/EquiJoin.java           |    2 +-
 .../core/query/lucene/join/JoinMerger.java         |    6 +-
 .../core/query/lucene/join/QueryEngine.java        |    5 +
 .../core/retention/RetentionRegistryImpl.java      |    4 +-
 .../core/security/DefaultAccessManager.java        |    4 +-
 .../authentication/AbstractLoginModule.java        |   44 +-
 .../authentication/CryptedSimpleCredentials.java   |  121 +-
 .../authentication/DefaultLoginModule.java         |   45 +-
 .../authentication/token/CompatTokenProvider.java  |  427 ++++++
 .../token/TokenBasedAuthentication.java            |  342 +----
 .../security/authentication/token/TokenInfo.java   |   40 +
 .../authentication/token/TokenProvider.java        |  478 +++++++
 .../authorization/AbstractACLTemplate.java         |   11 +
 .../authorization/AccessControlEditor.java         |    6 +-
 .../authorization/AccessControlEntryImpl.java      |   10 +-
 .../authorization/AccessControlProvider.java       |    2 +-
 .../AccessControlProviderFactory.java              |    4 +-
 .../core/security/authorization/GlobPattern.java   |   26 +-
 .../authorization/PrivilegeManagerImpl.java        |    9 +-
 .../security/authorization/PrivilegeRegistry.java  |   23 +-
 .../UnmodifiableAccessControlList.java             |    4 +
 .../core/security/authorization/acl/ACLEditor.java |    8 +-
 .../security/authorization/acl/ACLProvider.java    |   59 +-
 .../security/authorization/acl/ACLTemplate.java    |   91 +-
 .../authorization/acl/CachingEntryCollector.java   |  397 ++++--
 .../authorization/acl/CompiledPermissionsImpl.java |    8 +-
 .../core/security/authorization/acl/Entry.java     |  199 +++
 .../security/authorization/acl/EntryCollector.java |   27 +-
 .../security/authorization/acl/EntryFilter.java    |    3 +-
 .../authorization/acl/EntryFilterImpl.java         |   17 +-
 .../authorization/principalbased/ACLProvider.java  |    6 +-
 .../authorization/principalbased/ACLTemplate.java  |    1 -
 .../authorization/principalbased/EntriesCache.java |    2 +-
 .../principal/AbstractPrincipalProvider.java       |    2 +-
 .../principal/DefaultPrincipalProvider.java        |    8 +-
 .../security/principal/PrincipalManagerImpl.java   |    5 +-
 .../core/security/principal/PrincipalProvider.java |    2 +-
 .../principal/PrincipalProviderRegistry.java       |    2 +-
 .../core/security/user/AuthorizableImpl.java       |   25 +-
 .../jackrabbit/core/security/user/GroupImpl.java   |   89 +-
 .../core/security/user/MembershipCache.java        |  290 ++--
 .../core/security/user/PasswordUtility.java        |  268 ++++
 .../security/user/UserAccessControlProvider.java   |   14 +-
 .../jackrabbit/core/security/user/UserImpl.java    |   43 +-
 .../core/security/user/UserImporter.java           |  136 +-
 .../core/security/user/UserManagerConfig.java      |  102 ++
 .../core/security/user/UserManagerImpl.java        |  279 ++--
 .../security/user/UserPerWorkspaceUserManager.java |    3 +-
 .../core/security/user/XPathQueryEvaluator.java    |   53 +-
 .../security/user/action/AuthorizableAction.java   |   15 +-
 .../user/action/PasswordValidationAction.java      |   16 +-
 .../core/session/NodeNameNormalizer.java           |   59 +
 .../jackrabbit/core/session/SessionContext.java    |    4 +-
 .../core/session/SessionSaveOperation.java         |    4 +-
 .../jackrabbit/core/session/SessionState.java      |    4 +-
 .../apache/jackrabbit/core/state/ChangeLog.java    |   31 +
 .../jackrabbit/core/state/ChildNodeEntries.java    |    4 +-
 .../jackrabbit/core/state/ChildNodeEntry.java      |    2 +-
 .../jackrabbit/core/state/DefaultISMLocking.java   |    4 +-
 .../core/state/DummyUpdateEventChannel.java        |    2 +-
 .../core/state/FineGrainedISMLocking.java          |    4 +-
 .../apache/jackrabbit/core/state/ISMLocking.java   |    2 +-
 .../core/state/ItemStateReferenceCache.java        |    5 +-
 .../core/state/LocalItemStateManager.java          |   16 +-
 .../jackrabbit/core/state/MLRUItemStateCache.java  |    2 +-
 .../org/apache/jackrabbit/core/state/NameSet.java  |    4 +-
 .../jackrabbit/core/state/NodeReferences.java      |    2 +-
 .../apache/jackrabbit/core/state/NodeState.java    |   22 +-
 .../jackrabbit/core/state/NodeStateListener.java   |    2 +-
 .../jackrabbit/core/state/NodeStateMerger.java     |   18 +-
 .../core/state/SessionItemStateManager.java        |   16 +-
 .../core/state/SharedItemStateManager.java         |  125 +-
 .../jackrabbit/core/state/XAItemStateManager.java  |   18 +-
 .../jackrabbit/core/stats/QueryStatCore.java       |   40 -
 .../core/stats/QueryStatDtoComparator.java         |   31 -
 .../jackrabbit/core/stats/QueryStatDtoImpl.java    |  145 --
 .../stats/QueryStatDtoOccurrenceComparator.java    |   33 -
 .../jackrabbit/core/stats/QueryStatImpl.java       |  174 ---
 .../core/stats/RepositoryStatisticsImpl.java       |  105 --
 .../apache/jackrabbit/core/stats/StatManager.java  |    2 +
 .../jackrabbit/core/stats/TimeSeriesAverage.java   |   85 --
 .../jackrabbit/core/stats/TimeSeriesRecorder.java  |  158 ---
 .../jackrabbit/core/util/XAReentrantLock.java      |   80 ++
 .../XAReentrantWriterPreferenceReadWriteLock.java  |  130 ++
 .../jackrabbit/core/util/db/ConnectionFactory.java |  366 -----
 .../jackrabbit/core/util/db/ConnectionHelper.java  |  516 --------
 .../apache/jackrabbit/core/util/db/DbUtility.java  |   98 --
 .../core/util/db/PostgreSQLConnectionHelper.java   |   34 -
 .../jackrabbit/core/util/db/ResultSetWrapper.java  |   69 -
 .../jackrabbit/core/util/db/StreamWrapper.java     |   47 -
 .../jackrabbit/core/value/BLOBFileValue.java       |    4 +-
 .../jackrabbit/core/value/BLOBInDataStore.java     |   18 +-
 .../jackrabbit/core/value/InternalValue.java       |   26 +-
 .../core/value/InternalValueFactory.java           |   14 +-
 .../jackrabbit/core/value/ValueFactoryImpl.java    |    9 +
 .../jackrabbit/core/version/InternalBaseline.java  |    4 +-
 .../core/version/InternalFrozenNodeImpl.java       |    6 +-
 .../core/version/InternalFrozenVersionHistory.java |    2 +-
 .../core/version/InternalVersionHistoryImpl.java   |    5 +-
 .../core/version/InternalVersionImpl.java          |    4 +-
 .../core/version/InternalVersionManager.java       |    9 +
 .../core/version/InternalVersionManagerBase.java   |   64 +-
 .../core/version/InternalVersionManagerImpl.java   |   39 +-
 .../core/version/InternalXAVersionManager.java     |   46 +-
 .../jackrabbit/core/version/NodeStateEx.java       |   28 +-
 .../core/version/VersionHistoryImpl.java           |   16 +-
 .../core/version/VersionIteratorImpl.java          |    2 +-
 .../core/version/VersionManagerImplBase.java       |    2 +-
 .../core/version/VersionManagerImplConfig.java     |    8 +-
 .../core/version/VersionManagerImplMerge.java      |    7 +-
 .../core/version/VersionManagerImplRestore.java    |    4 +-
 .../jackrabbit/core/version/VersionSelector.java   |    4 +-
 .../jackrabbit/core/version/VersioningLock.java    |  151 +--
 .../jackrabbit/core/xml/AccessControlImporter.java |   63 +-
 .../jackrabbit/core/xml/BufferedStringValue.java   |    2 +-
 .../jackrabbit/core/xml/ClonedInputSource.java     |  334 ++---
 .../jackrabbit/core/xml/DocViewImportHandler.java  |    2 +-
 .../apache/jackrabbit/core/xml/ImportHandler.java  |    6 +-
 .../jackrabbit/core/xml/ProtectedNodeImporter.java |    6 +-
 .../jackrabbit/core/xml/WorkspaceImporter.java     |    4 +-
 .../core/JackrabbitRepositoryStub.properties       |   15 +-
 .../core/config/repository-2.4-elements.dtd        |   12 +-
 .../core/config/repository-2.6-elements.dtd        |  266 ++++
 .../jackrabbit/core/config/repository-2.6.dtd      |   64 +
 .../jackrabbit/core/data/db/azure.properties       |   17 -
 .../apache/jackrabbit/core/data/db/db2.properties  |   17 -
 .../jackrabbit/core/data/db/derby.properties       |   17 -
 .../apache/jackrabbit/core/data/db/h2.properties   |   18 -
 .../jackrabbit/core/data/db/ingres.properties      |   17 -
 .../jackrabbit/core/data/db/mssql.properties       |   17 -
 .../jackrabbit/core/data/db/mysql.properties       |   19 -
 .../jackrabbit/core/data/db/oracle.properties      |   18 -
 .../jackrabbit/core/data/db/postgresql.properties  |   20 -
 .../jackrabbit/core/data/db/sqlserver.properties   |   18 -
 .../jackrabbit/core/nodetype/builtin_nodetypes.cnd |   11 +-
 .../api/JackrabbitObservationManagerTest.java      |   59 +
 .../java/org/apache/jackrabbit/api/TestAll.java    |    1 +
 .../security/user/UserManagerCreateUserTest.java   |   29 +-
 .../api/security/user/UserManagerTest.java         |   56 +
 .../org/apache/jackrabbit/core/AddMoveTest.java    |   92 ++
 .../core/ConcurrentAddMoveRemoveTest.java          |  192 +++
 .../jackrabbit/core/ConcurrentCyclicMoveTest.java  |   92 ++
 .../jackrabbit/core/ConcurrentImportTest.java      |    2 +-
 .../core/ConcurrentNodeModificationTest.java       |    2 +-
 .../core/ConcurrentWorkspaceCopyTest.java          |  236 ++--
 .../apache/jackrabbit/core/ConsistencyCheck.java   |  100 +-
 .../org/apache/jackrabbit/core/MoveRemoveTest.java |   64 +
 .../java/org/apache/jackrabbit/core/MoveTest.java  |   48 +
 .../org/apache/jackrabbit/core/NodeImplTest.java   |   12 +
 .../jackrabbit/core/OverlappingNodeAddTest.java    |  264 ++--
 .../jackrabbit/core/ReadVersionsWhileModified.java |    2 +-
 .../jackrabbit/core/RemoveAddNodeWithUUIDTest.java |   71 +
 .../core/ReplacePropertyWhileOthersReadTest.java   |  137 ++
 .../test/java/org/apache/jackrabbit/core/Tail.java |   97 ++
 .../java/org/apache/jackrabbit/core/TestAll.java   |    6 +-
 .../org/apache/jackrabbit/core/TestHelper.java     |   38 +-
 .../java/org/apache/jackrabbit/core/XATest.java    |   37 +-
 .../core/cluster/ClusterDescriptorTest.java        |   68 +
 .../jackrabbit/core/cluster/DbClusterTest.java     |   14 +-
 .../core/cluster/DbClusterTestJCR3162.java         |   15 +-
 .../cluster/FailUpdateOnJournalExceptionTest.java  |  108 ++
 .../apache/jackrabbit/core/cluster/TestAll.java    |    1 +
 .../jackrabbit/core/cluster/TestJournal.java       |   62 +
 .../core/data/ConsistencyCheckerImplTest.java      |  500 +++++++
 .../apache/jackrabbit/core/data/DataStoreTest.java |   82 ++
 .../jackrabbit/core/data/GCEventListenerTest.java  |    4 +-
 .../jackrabbit/core/data/GCSubtreeMoveTest.java    |  207 +++
 .../org/apache/jackrabbit/core/data/GCThread.java  |    1 +
 .../jackrabbit/core/data/GarbageCollectorTest.java |   67 +-
 .../core/data/TempFileInputStreamTest.java         |   61 -
 .../org/apache/jackrabbit/core/data/TestAll.java   |    2 +-
 .../org/apache/jackrabbit/core/fs/TestAll.java     |   88 +-
 .../org/apache/jackrabbit/core/id/NodeIdTest.java  |  212 +--
 .../org/apache/jackrabbit/core/id/TestAll.java     |   82 +-
 .../integration/BackwardsCompatibilityTest.java    |  216 ---
 .../CachingHierarchyManagerConsistencyTest.java    |  127 ++
 .../core/integration/InterruptedQueryTest.java     |  134 ++
 .../jackrabbit/core/integration/NodeImplTest.java  |  186 +--
 .../core/integration/VersioningTest.java           |  430 +++---
 .../integration/daily/DailyIntegrationTest.java    |    4 +-
 .../jackrabbit/core/nodetype/xml/TestAll.java      |    8 +-
 .../jackrabbit/core/observation/MixinTest.java     |   84 +-
 .../jackrabbit/core/observation/ReorderTest.java   |    2 +-
 .../jackrabbit/core/observation/TestAll.java       |    1 +
 .../WarningOnSaveWithNotificationThreadTest.java   |   80 ++
 .../core/persistence/AutoFixCorruptNode.java       |  133 +-
 .../core/persistence/PersistenceManagerTest.java   |    6 +-
 .../util/BundleBindingRandomizedTest.java          |  231 ++++
 .../core/persistence/util/NodeCorruptionTest.java  |   88 ++
 .../jackrabbit/core/persistence/util/TestAll.java  |    2 +
 .../core/query/AbstractIndexingTest.java           |   63 +-
 .../jackrabbit/core/query/AbstractQueryTest.java   |   15 +-
 .../jackrabbit/core/query/ChildAxisQueryTest.java  |   35 +-
 .../core/query/DescendantSelfAxisTest.java         |   52 +
 .../apache/jackrabbit/core/query/ExcerptTest.java  |   60 +
 .../jackrabbit/core/query/FulltextQueryTest.java   |   38 +-
 .../core/query/IndexFormatVersionTest.java         |   59 -
 .../jackrabbit/core/query/IndexingRuleTest.java    |  187 ---
 .../jackrabbit/core/query/LargeResultSetTest.java  |   84 --
 .../core/query/LazyResultSetQueryTest.java         |   83 ++
 .../jackrabbit/core/query/LimitAndOffsetTest.java  |   31 +-
 .../apache/jackrabbit/core/query/MixinTest.java    |    5 +-
 .../apache/jackrabbit/core/query/OrderByTest.java  |   28 +-
 .../core/query/SQL2NodeLocalNameTest.java          |   20 +-
 .../jackrabbit/core/query/SQL2OrderByTest.java     |   20 +-
 .../jackrabbit/core/query/SQL2OuterJoinTest.java   |    4 +-
 .../jackrabbit/core/query/SQL2QueryResultTest.java |    6 +
 .../core/query/SQL2TooManyClausesTest.java         |    2 +-
 .../org/apache/jackrabbit/core/query/SQLTest.java  |    7 +-
 .../jackrabbit/core/query/ShareableNodeTest.java   |    6 +-
 .../jackrabbit/core/query/SimpleQueryTest.java     |   34 +
 .../jackrabbit/core/query/SynonymProviderTest.java |   63 -
 .../org/apache/jackrabbit/core/query/TestAll.java  |   10 +-
 .../core/query/lucene/ChainedTermEnumTest.java     |   36 +-
 .../core/query/lucene/DecimalConvertTest.java      |   24 +-
 .../core/query/lucene/IndexFormatVersionTest.java  |   54 +
 .../core/query/lucene/IndexInfosTest.java          |   77 ++
 .../core/query/lucene/IndexMigrationTest.java      |    2 +-
 .../core/query/lucene/IndexingAggregateTest.java   |    3 +-
 .../lucene/IndexingConfigurationImplTest.java      |   10 +-
 .../core/query/lucene/IndexingRuleTest.java        |  182 +++
 .../core/query/lucene/LargeResultSetTest.java      |  103 ++
 .../query/lucene/LazyTextExtractorFieldTest.java   |   51 +
 .../query/lucene/SQL2IndexingAggregateTest.java    |   14 +
 .../lucene/SearchIndexConsistencyCheckTest.java    |  327 +++++
 .../core/query/lucene/SearchIndexTest.java         |   38 +
 .../core/query/lucene/SynonymProviderTest.java     |   65 +
 .../jackrabbit/core/query/lucene/TestAll.java      |   11 +-
 .../core/query/lucene/TextExtractionQueryTest.java |   99 ++
 .../jackrabbit/core/query/lucene/UtilTest.java     |   67 +
 .../SimpleCredentialsAuthenticationTest.java       |    4 +
 .../token/CompatTokenProviderTest.java             |  192 +++
 .../security/authentication/token/TestAll.java     |    3 +
 .../token/TokenBasedAuthenticationCompatTest.java  |  232 ++++
 .../token/TokenBasedAuthenticationTest.java        |  161 ++-
 .../authentication/token/TokenProviderTest.java    |  198 +++
 .../authorization/AbstractACLTemplateTest.java     |   12 +-
 .../security/authorization/AbstractEntryTest.java  |   10 +
 .../authorization/AbstractLockManagementTest.java  |   47 +
 .../AbstractRepositoryOperationTest.java           |    5 +-
 .../authorization/acl/ACLTemplateEntryTest.java    |   89 ++
 .../authorization/acl/ACLTemplateTest.java         |    2 +-
 .../authorization/acl/EntryCollectorTest.java      |  184 ++-
 .../core/security/authorization/acl/EntryTest.java |  122 +-
 .../authorization/acl/ReadNodeTypeTest.java        |  118 ++
 .../core/security/authorization/acl/ReadTest.java  |  145 ++
 .../core/security/authorization/acl/TestAll.java   |    1 +
 .../core/security/authorization/acl/WriteTest.java |   23 +
 .../security/principal/PrincipalManagerTest.java   |   80 ++
 .../core/security/principal/TestAll.java           |    3 +-
 .../security/simple/SimpleSecurityManagerTest.java |   53 +
 .../jackrabbit/core/security/simple/TestAll.java   |   32 +
 .../core/security/user/AdministratorTest.java      |   14 +-
 .../core/security/user/AuthorizableActionTest.java |   44 +
 .../core/security/user/AuthorizableImplTest.java   |   81 +-
 .../user/DefaultPrincipalProviderTest.java         |   45 +-
 .../core/security/user/MembershipCacheTest.java    |  235 ++++
 .../core/security/user/NodeCreationTest.java       |    6 +-
 .../core/security/user/PasswordUtilityTest.java    |  170 +++
 .../jackrabbit/core/security/user/TestAll.java     |    1 +
 .../core/security/user/UserImplTest.java           |   22 +-
 .../core/security/user/UserImporterTest.java       |  216 ++-
 .../core/security/user/UserManagerImplTest.java    |   16 +-
 .../jackrabbit/core/stats/QueryStatCoreTest.java   |   79 ++
 .../org/apache/jackrabbit/core/stats/TestAll.java  |   42 +
 .../core/stats/TimeSeriesRecorderTest.java         |  123 --
 .../core/util/db/ConnectionFactoryTest.java        |    2 +-
 .../jackrabbit/core/value/BinaryValueTest.java     |    2 +-
 .../jackrabbit/core/value/ReferenceBinaryTest.java |   71 +
 .../org/apache/jackrabbit/core/value/TestAll.java  |    1 +
 .../core/version/CopyFrozenUuidTest.java           |   59 +
 .../core/version/RestoreNodeWithSNSTest.java       |  134 +-
 .../apache/jackrabbit/core/version/TestAll.java    |    1 +
 .../core/xml/AccessControlImporterTest.java        |   28 +
 jackrabbit-core/src/test/repository/repository.xml |    2 +-
 .../repository/workspaces/default/workspace.xml    |    2 +-
 .../workspaces/index-format-v1/index/_0/_2.cfs     |  Bin 0 -> 620 bytes
 .../workspaces/index-format-v1/index/_0/deletable  |  Bin 0 -> 4 bytes
 .../workspaces/index-format-v1/index/_0/segments   |  Bin 0 -> 27 bytes
 .../workspaces/index-format-v1/index/indexes       |  Bin 0 -> 12 bytes
 .../items/5a/9a/d0fcc7f542bbb435bcb9ed30a2e2.n     |  Bin 0 -> 50 bytes
 .../items/ca/fe/babecafebabecafebabecafebabe.n     |  Bin 0 -> 94 bytes
 .../items/de/ad/beefcafebabecafebabecafebabe.n     |  Bin 0 -> 119 bytes
 .../workspaces/index-format-v1/names.properties    |    5 +
 .../index-format-v1/namespaces.properties          |    6 +
 .../workspaces/index-format-v1/workspace.xml       |   33 +
 .../workspaces/index-format-v2/index/_0/_0.cfs     |  Bin 0 -> 590 bytes
 .../index-format-v2/index/_0/segments.gen          |  Bin 0 -> 20 bytes
 .../workspaces/index-format-v2/index/_0/segments_1 |  Bin 0 -> 20 bytes
 .../workspaces/index-format-v2/index/_0/segments_3 |  Bin 0 -> 41 bytes
 .../workspaces/index-format-v2/index/indexes       |  Bin 0 -> 12 bytes
 .../items/c9/bb/26c0edf0408b8ab22e88c1edc593.n     |  Bin 0 -> 50 bytes
 .../items/ca/fe/babecafebabecafebabecafebabe.n     |  Bin 0 -> 94 bytes
 .../items/de/ad/beefcafebabecafebabecafebabe.n     |  Bin 0 -> 119 bytes
 .../workspaces/index-format-v2/names.properties    |    5 +
 .../index-format-v2/namespaces.properties          |    6 +
 .../workspaces/index-format-v2/workspace.xml       |   33 +
 .../items/c9/bb/26c0edf0408b8ab22e88c1edc593.n     |  Bin 0 -> 50 bytes
 .../items/ca/fe/babecafebabecafebabecafebabe.n     |  Bin 0 -> 94 bytes
 .../items/de/ad/beefcafebabecafebabecafebabe.n     |  Bin 0 -> 119 bytes
 .../workspaces/index-format-v3/names.properties    |    5 +
 .../index-format-v3/namespaces.properties          |    6 +
 .../workspaces/index-format-v3/workspace.xml       |   33 +
 .../indexing-test/indexing-configuration.xml       |    1 +
 .../jackrabbit/core/cluster/repository-h2.xml      |   10 +-
 .../core/cluster/repository-with-test-journal.xml  |  164 +++
 .../apache/jackrabbit/core/config/workspace.xml    |   90 +-
 .../repository-with-SimpleFSDirectory.xml          |   74 ++
 .../core/nodetype/xml/test_ns_cnd_nodetypes.cnd    |    4 +-
 .../core/nodetype/xml/test_ns_xml_nodetypes.xml    |    4 +-
 .../xml/test_same_nt_name_cnd_nodetypes.cnd        |    2 +-
 .../xml/test_same_nt_name_xml_nodetypes.xml        |    2 +-
 .../org/apache/jackrabbit/core/query/test.rtf      |  157 +++
 .../core/security/simple/simple_repository.xml     |   78 ++
 .../jackrabbit/core/security/user/repository.xml   |   90 ++
 jackrabbit-data/pom.xml                            |  113 ++
 .../core/config/ConfigurationException.java        |    0
 .../jackrabbit/core/config/DataSourceConfig.java   |    0
 .../jackrabbit/core/data/AbstractDataRecord.java   |   94 ++
 .../jackrabbit/core/data/AbstractDataStore.java    |  135 ++
 .../jackrabbit/core/data/AsyncTouchCallback.java   |   41 +
 .../jackrabbit/core/data/AsyncTouchResult.java     |   50 +
 .../jackrabbit/core/data/AsyncUploadCache.java     |  352 +++++
 .../core/data/AsyncUploadCacheResult.java          |   70 +
 .../jackrabbit/core/data/AsyncUploadCallback.java  |   40 +
 .../jackrabbit/core/data/AsyncUploadResult.java    |   62 +
 .../org/apache/jackrabbit/core/data/Backend.java   |  186 +++
 .../jackrabbit/core/data/CachingDataRecord.java    |   62 +
 .../jackrabbit/core/data/CachingDataStore.java     | 1396 ++++++++++++++++++++
 .../apache/jackrabbit/core/data/CachingFDS.java    |   51 +
 .../jackrabbit/core/data/DataIdentifier.java       |   80 ++
 .../apache/jackrabbit/core/data/DataRecord.java    |   63 +
 .../org/apache/jackrabbit/core/data/DataStore.java |  154 +++
 .../jackrabbit/core/data/DataStoreException.java   |    0
 .../jackrabbit/core/data/DataStoreFactory.java     |   43 +
 .../org/apache/jackrabbit/core/data/FSBackend.java |  496 +++++++
 .../jackrabbit/core/data/FileDataRecord.java       |   71 +
 .../apache/jackrabbit/core/data/FileDataStore.java |  507 +++++++
 .../jackrabbit/core/data/LazyFileInputStream.java  |  167 +++
 .../apache/jackrabbit/core/data/LocalCache.java    |  643 +++++++++
 .../jackrabbit/core/data/MultiDataStore.java       |  722 ++++++++++
 .../jackrabbit/core/data/MultiDataStoreAware.java  |   38 +
 .../jackrabbit/core/data/ScanEventListener.java    |    0
 .../jackrabbit/core/data/db/DbDataRecord.java      |   71 +
 .../jackrabbit/core/data/db/DbDataStore.java       | 1013 ++++++++++++++
 .../jackrabbit/core/data/db/DbInputStream.java     |    0
 .../jackrabbit/core/data/db/DerbyDataStore.java    |    0
 .../data/db/ResettableTempFileInputStream.java     |   64 +
 .../core/data/db/TempFileInputStream.java          |   57 +
 .../core/data/util/NamedThreadFactory.java         |   44 +
 .../apache/jackrabbit/core/fs/BasedFileSystem.java |  187 +++
 .../org/apache/jackrabbit/core/fs/FileSystem.java  |    0
 .../jackrabbit/core/fs/FileSystemException.java    |    0
 .../jackrabbit/core/fs/FileSystemFactory.java      |   40 +
 .../jackrabbit/core/fs/FileSystemPathUtil.java     |  229 ++++
 .../jackrabbit/core/fs/FileSystemResource.java     |  226 ++++
 .../core/fs/RandomAccessOutputStream.java          |    0
 .../apache/jackrabbit/core/fs/local/FileUtil.java  |    0
 .../jackrabbit/core/fs/local/HandleMonitor.java    |    0
 .../jackrabbit/core/fs/local/LocalFileSystem.java  |  388 ++++++
 .../core/util/db/CheckSchemaOperation.java         |    0
 .../jackrabbit/core/util/db/ConnectionFactory.java |  377 ++++++
 .../jackrabbit/core/util/db/ConnectionHelper.java  |  599 +++++++++
 .../jackrabbit/core/util/db/DataSourceWrapper.java |    0
 .../jackrabbit/core/util/db/DatabaseAware.java     |    0
 .../apache/jackrabbit/core/util/db/DbUtility.java  |   98 ++
 .../core/util/db/DerbyConnectionHelper.java        |    0
 .../core/util/db/Oracle10R1ConnectionHelper.java   |    0
 .../core/util/db/OracleConnectionHelper.java       |    0
 .../core/util/db/PostgreSQLConnectionHelper.java   |   35 +
 .../jackrabbit/core/util/db/ResultSetWrapper.java  |   70 +
 .../jackrabbit/core/util/db/StreamWrapper.java     |  105 ++
 .../jackrabbit/data/core/InternalXAResource.java   |   72 +
 .../jackrabbit/data/core/TransactionContext.java   |  376 ++++++
 .../jackrabbit/data/core/TransactionException.java |   44 +
 .../jackrabbit/core/data/db/azure.properties       |   17 +
 .../apache/jackrabbit/core/data/db/db2.properties  |   17 +
 .../jackrabbit/core/data/db/derby.properties       |   17 +
 .../apache/jackrabbit/core/data/db/h2.properties   |   18 +
 .../jackrabbit/core/data/db/ingres.properties      |   17 +
 .../jackrabbit/core/data/db/mssql.properties       |   17 +
 .../jackrabbit/core/data/db/mysql.properties       |   19 +
 .../jackrabbit/core/data/db/oracle.properties      |   18 +
 .../jackrabbit/core/data/db/postgresql.properties  |   20 +
 .../jackrabbit/core/data/db/sqlserver.properties   |   18 +
 .../jackrabbit/core/data/InMemoryBackend.java      |  203 +++
 .../jackrabbit/core/data/InMemoryDataStore.java    |   51 +
 .../jackrabbit/core/data/RandomInputStream.java    |    0
 .../jackrabbit/core/data/TestCachingFDS.java       |   82 ++
 .../core/data/TestCachingFDSCacheOff.java          |   49 +
 .../apache/jackrabbit/core/data/TestCaseBase.java  |  682 ++++++++++
 .../jackrabbit/core/data/TestFileDataStore.java    |   70 +
 .../apache/jackrabbit/core/data/TestInMemDs.java   |   41 +
 .../jackrabbit/core/data/TestInMemDsCacheOff.java  |   40 +
 .../jackrabbit/core/data/TestLocalCache.java       |  402 ++++++
 .../data/db/ResettableTempFileInputStreamTest.java |   73 +
 .../core/data/db/TempFileInputStreamTest.java      |   42 +
 jackrabbit-data/src/test/resources/fs.properties   |   17 +
 .../src/test/resources/log4j.properties            |   29 +
 jackrabbit-jca/deploy/geronimo/geronimo-ra.xml     |   57 +
 jackrabbit-jca/pom.xml                             |    4 +-
 .../jackrabbit/jca/JCAConnectionRequestInfo.java   |   15 +-
 .../jackrabbit/jca/JCAManagedConnection.java       |  117 +-
 .../jca/JCAManagedConnectionFactory.java           |   60 +-
 .../jackrabbit/jca/JCARepositoryManager.java       |    8 +-
 .../jackrabbit/jca/TransactionBoundXAResource.java |    4 +-
 jackrabbit-jca/src/main/rar/META-INF/LICENSE       |  699 ++++++++++
 .../jackrabbit/jca/test/ConnectionFactoryTest.java |    3 +
 jackrabbit-jcr-client/pom.xml                      |   14 +-
 .../client/RepositoryFactoryImplTest.java          |   27 +
 jackrabbit-jcr-commons/pom.xml                     |    4 +-
 .../jackrabbit/commons/AbstractRepository.java     |   21 +
 .../apache/jackrabbit/commons/AbstractSession.java |   30 +-
 .../apache/jackrabbit/commons/ItemNameMatcher.java |  163 +++
 .../org/apache/jackrabbit/commons/JcrUtils.java    |  956 ++++++++++++--
 .../apache/jackrabbit/commons/NamespaceHelper.java |  480 +++----
 .../apache/jackrabbit/commons/cnd/CndImporter.java |   58 +-
 .../commons/cnd/CompactNodeTypeDefReader.java      |   26 +-
 .../jackrabbit/commons/flat/FilterIterator.java    |    2 +-
 .../commons/iterator/AbstractLazyIterator.java     |   68 +
 .../commons/iterator/FilteredRangeIterator.java    |    1 -
 .../jackrabbit/commons/iterator/NodeIterable.java  |    4 +
 .../commons/iterator/NodeIteratorAdapter.java      |    4 +
 .../commons/iterator/PropertyIterable.java         |    4 +
 .../commons/iterator/PropertyIteratorAdapter.java  |    4 +
 .../jackrabbit/commons/iterator/RowIterable.java   |    4 +
 .../jackrabbit/commons/iterator/package-info.java  |    2 +-
 .../commons/jackrabbit/SimpleReferenceBinary.java  |   68 +
 .../authorization/AccessControlUtils.java          |  390 ++++++
 .../jackrabbit/user/AuthorizableQueryManager.java  |   10 +-
 .../commons/observation/EventTracker.java          |  143 ++
 .../observation/JackrabbitEventTracker.java        |   45 +
 .../commons/observation/ListenerTracker.java       |  365 +++++
 .../apache/jackrabbit/commons/package-info.java    |    2 +-
 .../commons/packaging/ContentPackage.java          |   54 +-
 .../commons/packaging/ContentPackageExporter.java  |   54 +-
 .../commons/packaging/FilterContentPackage.java    |  410 +++---
 .../jackrabbit/commons/packaging/package-info.java |    2 +-
 .../org/apache/jackrabbit/commons/query/GQL.java   |  119 +-
 .../commons/query/qom/OperandEvaluator.java        |    1 -
 .../jackrabbit/commons/query/qom/Operator.java     |  322 ++---
 .../repository/SingletonRepositoryFactory.java     |   98 +-
 .../commons/visitor/FilteringItemVisitor.java      |  474 +++----
 .../commons/webdav/AtomFeedConstants.java          |  106 +-
 .../jackrabbit/commons/webdav/package-info.java    |    2 +-
 .../commons/xml/SerializingContentHandler.java     |    2 +-
 .../commons/xml/ToXmlContentHandler.java           |    2 +-
 .../org/apache/jackrabbit/stats/QueryStatCore.java |   40 +
 .../jackrabbit/stats/QueryStatDtoComparator.java   |   31 +
 .../apache/jackrabbit/stats/QueryStatDtoImpl.java  |  145 ++
 .../stats/QueryStatDtoOccurrenceComparator.java    |   33 +
 .../org/apache/jackrabbit/stats/QueryStatImpl.java |  186 +++
 .../jackrabbit/stats/RepositoryStatisticsImpl.java |  116 ++
 .../apache/jackrabbit/stats/TimeSeriesAverage.java |   85 ++
 .../org/apache/jackrabbit/stats/TimeSeriesMax.java |  142 ++
 .../jackrabbit/stats/TimeSeriesRecorder.java       |  161 +++
 .../jackrabbit/stats/TimeSeriesStatsUtil.java      |   56 +
 .../jackrabbit/stats/jmx/QueryStatManager.java     |  139 ++
 .../org/apache/jackrabbit/stats/package-info.java  |   18 +
 .../java/org/apache/jackrabbit/util/Base64.java    |    2 +-
 .../jackrabbit/util/ChildrenCollectorFilter.java   |  130 +-
 .../java/org/apache/jackrabbit/util/ISO8601.java   |   59 +-
 .../java/org/apache/jackrabbit/util/ISO9075.java   |    2 +-
 .../java/org/apache/jackrabbit/util/Locked.java    |    2 +-
 .../main/java/org/apache/jackrabbit/util/Text.java |    6 +-
 .../jackrabbit/util/TransientFileFactory.java      |   10 +-
 .../jackrabbit/util/WeakIdentityCollection.java    |    2 +-
 .../org/apache/jackrabbit/value/BinaryImpl.java    |    2 +-
 .../org/apache/jackrabbit/value/BinaryValue.java   |    2 +-
 .../org/apache/jackrabbit/value/BooleanValue.java  |    2 +-
 .../org/apache/jackrabbit/value/DateValue.java     |    4 +-
 .../org/apache/jackrabbit/value/DecimalValue.java  |    2 +-
 .../org/apache/jackrabbit/value/DoubleValue.java   |    2 +-
 .../org/apache/jackrabbit/value/LongValue.java     |    2 +-
 .../org/apache/jackrabbit/value/NameValue.java     |    4 +-
 .../org/apache/jackrabbit/value/PathValue.java     |    4 +-
 .../apache/jackrabbit/value/ReferenceValue.java    |    4 +-
 .../org/apache/jackrabbit/value/StringValue.java   |    2 +-
 .../java/org/apache/jackrabbit/value/URIValue.java |    4 +-
 .../org/apache/jackrabbit/value/ValueHelper.java   |   27 +
 .../jackrabbit/value/WeakReferenceValue.java       |    4 +-
 .../apache/jackrabbit/commons/JcrUtilsTest.java    |   17 +
 .../jackrabbit/commons/json/JsonUtilTest.java      |   46 +-
 .../apache/jackrabbit/commons/query/GQLTest.java   |   44 +
 .../stats/RepositoryStatisticsImplTest.java        |   59 +
 .../jackrabbit/stats/TimeSeriesRecorderTest.java   |  122 ++
 jackrabbit-jcr-rmi/pom.xml                         |  204 +--
 .../rmi/client/BrokenRemoteRepository.java         |   45 +
 .../rmi/client/ClientAdapterFactory.java           |   12 +-
 .../apache/jackrabbit/rmi/client/ClientLock.java   |   15 +-
 .../apache/jackrabbit/rmi/client/ClientNode.java   |   57 +-
 .../rmi/client/ClientNodeDefinition.java           |   14 +-
 .../jackrabbit/rmi/client/ClientNodeType.java      |   43 +-
 .../rmi/client/ClientNodeTypeManager.java          |   12 +-
 .../rmi/client/ClientObservationManager.java       |    6 +-
 .../rmi/client/ClientPropertyDefinition.java       |   21 +-
 .../apache/jackrabbit/rmi/client/ClientQuery.java  |   29 +-
 .../jackrabbit/rmi/client/ClientQueryManager.java  |    2 +-
 .../jackrabbit/rmi/client/ClientQueryResult.java   |   10 +-
 .../jackrabbit/rmi/client/ClientRepository.java    |    9 +-
 .../apache/jackrabbit/rmi/client/ClientRow.java    |   54 +-
 .../jackrabbit/rmi/client/ClientSession.java       |    4 +-
 .../jackrabbit/rmi/client/ClientVersion.java       |   32 +-
 .../rmi/client/ClientVersionHistory.java           |   41 +-
 .../rmi/client/ClientVersionManager.java           |   94 +-
 .../jackrabbit/rmi/client/ClientWorkspace.java     |    4 +-
 .../jackrabbit/rmi/client/LocalAdapterFactory.java |    6 +-
 .../rmi/client/SafeClientRepository.java           |   90 +-
 .../rmi/client/iterator/ClientRowIterator.java     |    9 +-
 .../rmi/client/security/ClientPrivilege.java       |    4 -
 .../rmi/observation/ClientEventPoll.java           |   67 +-
 .../rmi/remote/RemoteEventCollection.java          |   64 +-
 .../apache/jackrabbit/rmi/remote/RemoteLock.java   |   19 +
 .../apache/jackrabbit/rmi/remote/RemoteNode.java   |   88 ++
 .../rmi/remote/RemoteNodeDefinition.java           |   20 +
 .../jackrabbit/rmi/remote/RemoteNodeType.java      |   60 +
 .../rmi/remote/RemotePropertyDefinition.java       |   30 +
 .../apache/jackrabbit/rmi/remote/RemoteQuery.java  |   35 +
 .../jackrabbit/rmi/remote/RemoteQueryResult.java   |    8 +
 .../jackrabbit/rmi/remote/RemoteRepository.java    |   42 +
 .../apache/jackrabbit/rmi/remote/RemoteRow.java    |   57 +
 .../jackrabbit/rmi/remote/RemoteVersion.java       |   36 +-
 .../rmi/remote/RemoteVersionHistory.java           |   64 +-
 .../rmi/remote/RemoteVersionManager.java           |  108 +-
 .../jackrabbit/rmi/remote/SerializableXid.java     |  142 +-
 .../AbstractRemoteRepositoryFactory.java           |    1 -
 .../rmi/repository/RmiRepositoryFactory.java       |  144 +-
 .../rmi/server/RemoteAdapterFactory.java           |    2 +-
 .../rmi/server/ServerAdapterFactory.java           |   26 +-
 .../rmi/server/ServerEventCollection.java          |   50 +-
 .../apache/jackrabbit/rmi/server/ServerLock.java   |   12 +-
 .../apache/jackrabbit/rmi/server/ServerNode.java   |   81 ++
 .../rmi/server/ServerNodeDefinition.java           |   10 +
 .../jackrabbit/rmi/server/ServerNodeType.java      |   31 +
 .../rmi/server/ServerObservationManager.java       |    8 +-
 .../rmi/server/ServerPropertyDefinition.java       |   16 +-
 .../apache/jackrabbit/rmi/server/ServerQuery.java  |   27 +
 .../jackrabbit/rmi/server/ServerQueryResult.java   |    5 +
 .../jackrabbit/rmi/server/ServerRepository.java    |   30 +
 .../apache/jackrabbit/rmi/server/ServerRow.java    |   73 +-
 .../jackrabbit/rmi/server/ServerVersion.java       |   53 +-
 .../rmi/server/ServerVersionHistory.java           |   43 +
 .../rmi/server/ServerVersionManager.java           |  117 +-
 .../jackrabbit/rmi/server/ServerWorkspace.java     |    2 +-
 .../apache/jackrabbit/rmi/observation/package.html |    2 +-
 .../apache/jackrabbit/rmi/RepositoryStubImpl.java  |    2 +-
 jackrabbit-jcr-server/pom.xml                      |   54 +-
 .../server/BasicCredentialsProvider.java           |    2 +-
 .../jackrabbit/server/SessionProviderImpl.java     |  113 +-
 .../jackrabbit/server/io/DefaultHandler.java       |    2 +-
 .../server/io/DirListingExportHandler.java         |    4 +-
 .../jackrabbit/server/io/PropertyHandler.java      |    2 +-
 .../jackrabbit/server/jcr/JCRWebdavServer.java     |   67 +-
 .../org/apache/jackrabbit/server/package-info.java |   18 +
 .../server/remoting/davex/AclRemoveHandler.java    |   61 +
 .../server/remoting/davex/DavexServletService.java |  113 +-
 .../server/remoting/davex/JcrRemotingServlet.java  |   31 +-
 .../server/remoting/davex/JsonDiffHandler.java     |  378 +++++-
 .../server/remoting/davex/JsonWriter.java          |   41 +-
 .../remoting/davex/ProtectedItemRemoveHandler.java |   28 +
 .../remoting/davex/ProtectedRemoveManager.java     |  105 ++
 .../jackrabbit/server/util/HttpMultipartPost.java  |    4 +-
 .../apache/jackrabbit/server/util/RequestData.java |    4 +-
 .../webdav/jcr/AbstractItemResource.java           |   54 +-
 .../jackrabbit/webdav/jcr/AbstractResource.java    |   54 +-
 .../webdav/jcr/DavLocatorFactoryImpl.java          |    2 +-
 .../webdav/jcr/DavResourceFactoryImpl.java         |    2 +-
 .../webdav/jcr/DefaultItemCollection.java          |   37 +-
 .../jackrabbit/webdav/jcr/DefaultItemResource.java |    5 +
 .../webdav/jcr/EventJournalResourceImpl.java       |  956 +++++++-------
 .../webdav/jcr/ItemResourceConstants.java          |   23 +-
 .../jackrabbit/webdav/jcr/JcrDavSession.java       |   33 +-
 .../jackrabbit/webdav/jcr/RootCollection.java      |   62 +-
 .../jcr/VersionControlledItemCollection.java       |   27 +-
 .../webdav/jcr/WorkspaceResourceImpl.java          |   80 +-
 .../jackrabbit/webdav/jcr/lock/JcrActiveLock.java  |   49 +-
 .../webdav/jcr/lock/LockTokenMapper.java           |   74 ++
 .../webdav/jcr/observation/SubscriptionImpl.java   |   28 +-
 .../webdav/jcr/property/JcrDavPropertyNameSet.java |    2 -
 .../webdav/jcr/search/SearchResourceImpl.java      |  135 +-
 .../security/JcrSupportedPrivilegesProperty.java   |  122 ++
 .../jcr/security/JcrUserPrivilegesProperty.java    |   60 +
 .../webdav/jcr/security/SecurityUtils.java         |   30 +
 .../webdav/jcr/transaction/TxLockManagerImpl.java  |    2 +-
 .../jcr/version/report/ExportViewReport.java       |    2 +-
 .../jcr/version/report/JcrPrivilegeReport.java     |   42 +-
 .../jcr/version/report/LocateByUuidReport.java     |    2 +-
 .../report/LocateCorrespondingNodeReport.java      |    2 +-
 .../jackrabbit/webdav/simple/DavResourceImpl.java  |   23 +-
 .../webdav/simple/DeltaVResourceImpl.java          |   18 +-
 .../webdav/simple/LocatorFactoryImplEx.java        |    2 +-
 .../simple/VersionControlledResourceImpl.java      |    4 +-
 .../remoting/davex/JsonDiffHandlerImportTest.java  |  176 +++
 .../server/remoting/davex/JsonDiffHandlerTest.java |   53 +-
 .../webdav/jcr/LockTimeOutFormatTest.java          |  124 ++
 .../webdav/jcr/LockTokenMappingTest.java           |  107 ++
 .../webdav/jcr/security/AbstractSecurityTest.java  |   40 +
 .../JcrSupportedPrivilegePropertyTest.java         |   46 +
 .../security/JcrUserPrivilegesPropertyTest.java    |   69 +
 .../test/resources/protectedHandlers.properties    |   17 +
 .../src/test/resources/repository.xml              |  166 +++
 .../test/resources/repositoryStubImpl.properties   |   23 +
 jackrabbit-jcr-servlet/pom.xml                     |    8 +-
 .../servlet/AbstractRepositoryServlet.java         |   14 +-
 .../servlet/ContextRepositoryServlet.java          |    4 +-
 .../servlet/FilterRepositoryFactory.java           |  190 +--
 .../servlet/login/AbstractLoginFilter.java         |  226 ++--
 .../jackrabbit/servlet/login/BasicLoginFilter.java |   76 +-
 .../servlet/login/ContainerLoginFilter.java        |  128 +-
 .../jackrabbit/servlet/login/NullLoginFilter.java  |   86 +-
 jackrabbit-jcr-tests/pom.xml                       |    2 +-
 .../apache/jackrabbit/test/AbstractJCRTest.java    |   92 +-
 .../java/org/apache/jackrabbit/test/ISO8601.java   |    4 +-
 .../apache/jackrabbit/test/JNDIRepositoryStub.java |    5 +-
 .../jackrabbit/test/RepositoryHelperPool.java      |   10 +
 .../jackrabbit/test/RepositoryHelperPoolImpl.java  |   32 +-
 .../org/apache/jackrabbit/test/RepositoryStub.java |   25 +-
 .../jackrabbit/test/RepositoryStubException.java   |    9 +
 .../jackrabbit/test/api/AbstractImportXmlTest.java |   37 +-
 .../test/api/AbstractWorkspaceCopyTest.java        |    2 +-
 .../apache/jackrabbit/test/api/AddNodeTest.java    |   67 +-
 .../jackrabbit/test/api/BinaryPropertyTest.java    |    2 +-
 .../jackrabbit/test/api/ExportDocViewTest.java     |    2 -
 .../jackrabbit/test/api/NamespaceRegistryTest.java |    6 +-
 .../jackrabbit/test/api/NodeAddMixinTest.java      |    8 +-
 .../jackrabbit/test/api/NodeCanAddMixinTest.java   |    4 +-
 .../test/api/NodeDiscoveringNodeTypesTest.java     |   19 +-
 .../test/api/NodeItemIsModifiedTest.java           |    6 +-
 .../jackrabbit/test/api/NodeItemIsNewTest.java     |    2 +-
 .../apache/jackrabbit/test/api/NodeMixinUtil.java  |   20 +-
 .../jackrabbit/test/api/NodeRemoveMixinTest.java   |   14 +-
 .../test/api/NodeSetPrimaryTypeTest.java           |    2 +-
 .../org/apache/jackrabbit/test/api/NodeTest.java   |   38 +-
 .../test/api/PropertyItemIsModifiedTest.java       |    2 +-
 .../jackrabbit/test/api/PropertyItemIsNewTest.java |    2 +-
 .../apache/jackrabbit/test/api/PropertyTest.java   |    2 +-
 .../apache/jackrabbit/test/api/ReferencesTest.java |   28 +-
 .../jackrabbit/test/api/SerializationTest.java     |    7 +-
 .../jackrabbit/test/api/SessionRemoveItemTest.java |    5 +-
 .../apache/jackrabbit/test/api/SessionTest.java    |   35 +-
 .../test/api/SetPropertyAssumeTypeTest.java        |    8 +-
 .../test/api/SetPropertyBooleanTest.java           |   10 +-
 .../test/api/SetPropertyCalendarTest.java          |   10 +-
 ...etPropertyConstraintViolationExceptionTest.java |   44 +-
 .../jackrabbit/test/api/SetPropertyDoubleTest.java |   23 +-
 .../test/api/SetPropertyInputStreamTest.java       |   12 +-
 .../jackrabbit/test/api/SetPropertyLongTest.java   |   10 +-
 .../jackrabbit/test/api/SetPropertyNodeTest.java   |   12 +-
 .../jackrabbit/test/api/SetPropertyStringTest.java |   78 +-
 .../jackrabbit/test/api/SetPropertyValueTest.java  |   78 +-
 .../jackrabbit/test/api/SetValueBinaryTest.java    |    2 +-
 .../SetValueConstraintViolationExceptionTest.java  |   96 +-
 .../test/api/SetValueInputStreamTest.java          |    6 +-
 .../jackrabbit/test/api/SetValueStringTest.java    |    2 +-
 .../test/api/SetValueValueFormatExceptionTest.java |    4 +-
 .../test/api/SetValueVersionExceptionTest.java     |   40 +-
 .../jackrabbit/test/api/ShareableNodeTest.java     |  189 ++-
 .../apache/jackrabbit/test/api/TreeComparator.java |    2 +-
 .../jackrabbit/test/api/ValueFactoryTest.java      |    2 +-
 .../test/api/WorkspaceCopySameNameSibsTest.java    |    4 +-
 .../jackrabbit/test/api/WorkspaceCopyTest.java     |    2 +-
 .../test/api/WorkspaceMoveSameNameSibsTest.java    |    4 +-
 .../jackrabbit/test/api/WorkspaceMoveTest.java     |    2 +-
 .../jackrabbit/test/api/lock/AbstractLockTest.java |   38 +-
 .../jackrabbit/test/api/lock/DeepLockTest.java     |    6 +-
 .../jackrabbit/test/api/lock/LockManagerTest.java  |   10 +-
 .../apache/jackrabbit/test/api/lock/LockTest.java  |   55 +-
 .../test/api/lock/OpenScopedLockTest.java          |    5 -
 .../test/api/lock/SessionScopedLockTest.java       |    4 -
 .../test/api/lock/SetValueLockExceptionTest.java   |    2 +-
 .../CanAddChildNodeCallWithNodeTypeTest.java       |   25 +-
 .../jackrabbit/test/api/nodetype/NodeTypeTest.java |   54 +-
 .../jackrabbit/test/api/nodetype/NodeTypeUtil.java |  111 ++
 .../test/api/nodetype/PropertyDefTest.java         |    2 +-
 .../test/api/observation/AddEventListenerTest.java |   18 +-
 .../test/api/observation/EventIteratorTest.java    |    6 +-
 .../test/api/observation/EventJournalTest.java     |    2 +
 .../jackrabbit/test/api/observation/EventTest.java |    6 +-
 .../test/api/observation/GetDateTest.java          |    2 +-
 .../test/api/observation/GetIdentifierTest.java    |   18 +-
 .../test/api/observation/GetInfoTest.java          |   41 +-
 .../test/api/observation/GetUserDataTest.java      |    6 +-
 .../test/api/observation/LockingTest.java          |    4 +-
 .../test/api/observation/NodeAddedTest.java        |    8 +-
 .../test/api/observation/NodeMovedTest.java        |   12 +-
 .../test/api/observation/NodeRemovedTest.java      |   14 +-
 .../test/api/observation/NodeReorderTest.java      |   91 +-
 .../test/api/observation/PropertyAddedTest.java    |   10 +-
 .../test/api/observation/PropertyChangedTest.java  |   16 +-
 .../test/api/observation/PropertyRemovedTest.java  |    8 +-
 .../api/observation/WorkspaceOperationTest.java    |   10 +-
 .../test/api/query/AbstractOrderByTest.java        |   10 +-
 .../test/api/query/AbstractQueryLevel2Test.java    |    6 +-
 .../test/api/query/AbstractQueryTest.java          |   99 +-
 .../jackrabbit/test/api/query/ElementTest.java     |   16 +-
 .../jackrabbit/test/api/query/GetLanguageTest.java |   10 +-
 .../query/GetPersistentQueryPathLevel1Test.java    |    2 +-
 .../test/api/query/GetPersistentQueryPathTest.java |    2 +-
 .../test/api/query/GetPropertyNamesTest.java       |    2 +-
 .../test/api/query/GetStatementTest.java           |    2 +-
 .../api/query/GetSupportedQueryLanguagesTest.java  |    2 +-
 .../test/api/query/OrderByMultiTypeTest.java       |   16 +-
 .../jackrabbit/test/api/query/PredicatesTest.java  |   11 +-
 .../api/query/QueryResultNodeIteratorTest.java     |   17 +-
 .../jackrabbit/test/api/query/SQLJcrPathTest.java  |    2 +-
 .../jackrabbit/test/api/query/SQLJoinTest.java     |    8 +-
 .../jackrabbit/test/api/query/SQLOrderByTest.java  |    9 +-
 .../jackrabbit/test/api/query/SQLPathTest.java     |   18 +-
 .../test/api/query/SQLQueryLevel2Test.java         |    7 +-
 .../apache/jackrabbit/test/api/query/SaveTest.java |    9 +-
 .../test/api/query/SimpleSelectionTest.java        |    2 +-
 .../jackrabbit/test/api/query/TextNodeTest.java    |    8 +-
 .../test/api/query/XPathDocOrderTest.java          |    9 +-
 .../test/api/query/XPathJcrPathTest.java           |    2 +-
 .../test/api/query/XPathOrderByTest.java           |    8 +-
 .../test/api/query/XPathPosIndexTest.java          |    3 +-
 .../test/api/query/XPathQueryLevel2Test.java       |    7 +-
 .../jackrabbit/test/api/query/qom/ColumnTest.java  |   18 +-
 .../test/api/query/qom/EquiJoinConditionTest.java  |    4 +-
 .../test/api/query/qom/GetQueryTest.java           |   10 +-
 .../test/api/retention/HoldEffectTest.java         |    1 -
 .../jackrabbit/test/api/retention/HoldTest.java    |    8 +-
 .../api/retention/RetentionPolicyEffectTest.java   |    1 -
 .../test/api/retention/RetentionPolicyTest.java    |    6 +-
 .../api/security/RSessionAccessControlTest.java    |    5 +-
 .../apache/jackrabbit/test/api/util/ISO9075.java   |    2 +-
 .../org/apache/jackrabbit/test/api/util/Text.java  |    6 +-
 .../test/api/version/AbstractMergeTest.java        |    6 +-
 .../api/version/AbstractOnParentVersionTest.java   |    2 +-
 .../test/api/version/AbstractVersionTest.java      |    3 +
 .../jackrabbit/test/api/version/CheckinTest.java   |    9 +
 .../jackrabbit/test/api/version/CheckoutTest.java  |    4 +
 .../jackrabbit/test/api/version/CopyTest.java      |    2 -
 .../test/api/version/GetReferencesNodeTest.java    |    9 +-
 .../test/api/version/MergeActivityTest.java        |    6 +-
 .../test/api/version/MergeCancelMergeTest.java     |    1 +
 .../jackrabbit/test/api/version/MergeNodeTest.java |    9 +
 .../test/api/version/MergeSubNodeTest.java         |    7 +-
 .../test/api/version/OnParentVersionAbortTest.java |    2 +-
 .../test/api/version/OnParentVersionCopyTest.java  |    1 +
 .../test/api/version/RemoveVersionTest.java        |    2 +-
 .../jackrabbit/test/api/version/RestoreTest.java   |   33 +-
 .../version/SessionMoveVersionExceptionTest.java   |    2 +-
 .../test/api/version/VersionHistoryTest.java       |   17 +-
 .../test/api/version/VersionLabelTest.java         |   66 +-
 .../version/WorkspaceMoveVersionExceptionTest.java |    6 +-
 .../test/api/version/WorkspaceRestoreTest.java     |    7 +
 .../api/version/simple/AbstractVersionTest.java    |    3 +
 .../test/api/version/simple/CheckinTest.java       |    6 +
 .../test/api/version/simple/RestoreTest.java       |   18 +
 jackrabbit-jcr2dav/pom.xml                         |   50 +-
 .../resources/accessControlProvider.properties     |   16 +
 .../apache/jackrabbit/jcr2dav/ConformanceTest.java |    2 +
 .../jackrabbit/jcr2dav/RepositoryStubImpl.java     |   79 +-
 .../src/test/resources/repository.xml              |  166 +++
 .../test/resources/repositoryStubImpl.properties   |   20 +
 jackrabbit-jcr2spi/pom.xml                         |   21 +-
 .../jackrabbit/jcr2spi/ItemLifeCycleListener.java  |    2 +-
 .../org/apache/jackrabbit/jcr2spi/ItemManager.java |   13 +-
 .../apache/jackrabbit/jcr2spi/ItemManagerImpl.java |   12 +-
 .../jcr2spi/Jcr2spiRepositoryFactory.java          |   88 +-
 .../jackrabbit/jcr2spi/LazyItemIterator.java       |   16 +-
 .../apache/jackrabbit/jcr2spi/ManagerProvider.java |    3 +
 .../org/apache/jackrabbit/jcr2spi/NodeImpl.java    |    4 +-
 .../org/apache/jackrabbit/jcr2spi/SessionImpl.java |   19 +-
 .../apache/jackrabbit/jcr2spi/WorkspaceImpl.java   |   35 +-
 .../jackrabbit/jcr2spi/WorkspaceManager.java       |   72 +-
 .../jcr2spi/config/RepositoryConfig.java           |    2 +
 .../jcr2spi/hierarchy/NodeEntryImpl.java           |    4 +-
 .../jcr2spi/hierarchy/PropertyEntryImpl.java       |    2 +-
 .../jackrabbit/jcr2spi/lock/LockManagerImpl.java   |    6 +-
 .../jcr2spi/nodetype/BitsetENTCacheImpl.java       |    4 +-
 .../jcr2spi/nodetype/EffectiveNodeTypeCache.java   |    2 +-
 .../jcr2spi/nodetype/EffectiveNodeTypeImpl.java    |    4 +-
 .../jackrabbit/jcr2spi/nodetype/NodeTypeImpl.java  |    4 +-
 .../jackrabbit/jcr2spi/operation/AddNode.java      |    5 +-
 .../jackrabbit/jcr2spi/operation/AddProperty.java  |    8 +-
 .../jcr2spi/operation/IgnoreOperation.java         |   25 +
 .../apache/jackrabbit/jcr2spi/operation/Merge.java |    4 +-
 .../jcr2spi/operation/OperationVisitor.java        |    4 +-
 .../jackrabbit/jcr2spi/operation/Remove.java       |   14 +-
 .../jackrabbit/jcr2spi/operation/SetTree.java      |  196 +++
 .../jcr2spi/operation/WorkspaceImport.java         |  220 +--
 .../jackrabbit/jcr2spi/query/QueryResultImpl.java  |   10 +-
 .../jackrabbit/jcr2spi/query/RowIteratorImpl.java  |    8 +-
 .../authorization/AccessControlProvider.java       |   73 +
 .../authorization/AccessControlProviderStub.java   |  117 ++
 .../security/authorization/PrivilegeImpl.java      |  133 ++
 .../jackrabbit/AccessControlConstants.java         |   62 +
 .../jackrabbit/acl/AccessControlEntryImpl.java     |  227 ++++
 .../jackrabbit/acl/AccessControlListImpl.java      |  276 ++++
 .../jackrabbit/acl/AccessControlManagerImpl.java   |  440 ++++++
 .../jackrabbit/acl/AccessControlProviderImpl.java  |  119 ++
 .../apache/jackrabbit/jcr2spi/state/ItemState.java |    7 +-
 .../jcr2spi/state/ItemStateValidator.java          |    2 +-
 .../jcr2spi/state/SessionItemStateManager.java     |   15 +-
 .../jcr2spi/xml/DocViewImportHandler.java          |    2 +-
 .../jackrabbit/jcr2spi/xml/ImportHandler.java      |    6 +-
 .../jcr2spi/xml/TargetImportHandler.java           |    6 +-
 .../jackrabbit/jcr2spi/AbstractJCR2SPITest.java    |   35 +
 .../jcr2spi/AbstractRepositoryConfig.java          |    5 +
 .../jackrabbit/jcr2spi/ReorderMixedTest.java       |   90 +-
 .../apache/jackrabbit/jcr2spi/ReorderMoveTest.java |  514 +++----
 .../jackrabbit/jcr2spi/ReorderNewAndSavedTest.java |  128 +-
 .../jackrabbit/jcr2spi/ReorderNewSNSTest.java      |  142 +-
 .../apache/jackrabbit/jcr2spi/ReorderNewTest.java  |  142 +-
 .../jcr2spi/ReorderReferenceableSNSTest.java       |   98 +-
 .../apache/jackrabbit/jcr2spi/ReorderSNSTest.java  |  146 +-
 .../org/apache/jackrabbit/jcr2spi/ReorderTest.java |  332 ++---
 .../jcr2spi/lock/OpenScopedLockTest.java           |   68 +-
 .../jcr2spi/security/Jcr2SpiSecurityTestSuite.java |   32 +
 .../jackrabbit/acl/AccessControlListImplTest.java  |  177 +++
 .../acl/AccessControlManagerImplTest.java          |  213 +++
 .../authorization/jackrabbit/acl/TestAll.java      |   33 +
 .../jackrabbit/jcr2spi/version/LabelTest.java      |  154 +--
 .../resources/accessControlProvider.properties     |   16 +
 jackrabbit-parent/pom.xml                          |   56 +-
 jackrabbit-spi-commons/pom.xml                     |   10 +-
 .../spi/commons/AbstractRepositoryService.java     |    9 +
 .../apache/jackrabbit/spi/commons/EventImpl.java   |    3 +-
 .../jackrabbit/spi/commons/SerializableBatch.java  |   24 +
 .../jackrabbit/spi/commons/SessionExtensions.java  |   64 +-
 .../spi/commons/batch/ChangeLogImpl.java           |    5 +
 .../spi/commons/batch/ConsolidatingChangeLog.java  |   47 +
 .../jackrabbit/spi/commons/batch/Operations.java   |   71 +-
 .../commons/conversion/CachingNameResolver.java    |    3 +
 .../spi/commons/conversion/PathParser.java         |   21 +-
 .../spi/commons/iterator/Transformer.java          |    2 +-
 .../apache/jackrabbit/spi/commons/lock/Locked.java |    2 +-
 .../spi/commons/logging/AbstractLogger.java        |    4 +-
 .../spi/commons/logging/BatchLogger.java           |   11 +-
 .../commons/logging/RepositoryServiceLogger.java   |   35 +
 .../jackrabbit/spi/commons/name/NameConstants.java |    9 +-
 .../spi/commons/name/NameFactoryImpl.java          |    2 +-
 .../namespace/RegistryNamespaceResolver.java       |  132 +-
 .../spi/commons/nodetype/NodeTypeDefDiff.java      |  185 ++-
 .../commons/nodetype/NodeTypeDefinitionImpl.java   |    2 +-
 .../nodetype/constraint/ValueConstraint.java       |    4 +-
 .../jackrabbit/spi/commons/package-info.java       |    2 +-
 .../spi/commons/query/LocationStepQueryNode.java   |    2 +-
 .../jackrabbit/spi/commons/query/QueryParser.java  |    2 +-
 .../spi/commons/query/qom/ColumnImpl.java          |    2 +-
 .../query/qom/QueryObjectModelFactoryImpl.java     |    8 +-
 .../spi/commons/query/qom/SelectorImpl.java        |    4 +-
 .../spi/commons/query/sql/JCRSQLQueryBuilder.java  |   10 +-
 .../spi/commons/query/xpath/QueryFormat.java       |    2 +-
 .../spi/commons/query/xpath/XPathQueryBuilder.java |   29 +-
 .../jackrabbit/spi/commons/tree/AbstractTree.java  |   77 ++
 .../spi/commons/value/AbstractQValue.java          |   34 +-
 .../spi/commons/value/QValueFactoryImpl.java       |    4 +-
 .../spi/commons/value/ValueFactoryQImpl.java       |   24 +-
 .../spi/commons/conversion/PathParserTest.java     |    5 +-
 .../spi/commons/nodetype/NodeTypeDefDiffTest.java  |  139 ++
 .../jackrabbit/spi/commons/nodetype/TestAll.java   |    1 +
 .../spi/commons/query/xpath/QueryFormatTest.java   |    8 +
 jackrabbit-spi/pom.xml                             |    2 +-
 .../main/java/org/apache/jackrabbit/spi/Batch.java |    9 +
 .../java/org/apache/jackrabbit/spi/ItemId.java     |    2 +-
 .../main/java/org/apache/jackrabbit/spi/Name.java  |    6 +-
 .../org/apache/jackrabbit/spi/NameFactory.java     |    2 +-
 .../main/java/org/apache/jackrabbit/spi/Path.java  |   20 +-
 .../org/apache/jackrabbit/spi/PathFactory.java     |    4 +-
 .../apache/jackrabbit/spi/QNodeTypeDefinition.java |    2 +-
 .../apache/jackrabbit/spi/QValueConstraint.java    |    2 +-
 .../java/org/apache/jackrabbit/spi/QueryInfo.java  |    6 +-
 .../org/apache/jackrabbit/spi/QueryResultRow.java  |    4 +-
 .../apache/jackrabbit/spi/RepositoryService.java   |   53 +-
 .../org/apache/jackrabbit/spi/SessionInfo.java     |    1 -
 .../main/java/org/apache/jackrabbit/spi/Tree.java  |   38 +
 .../org/apache/jackrabbit/spi/package-info.java    |    2 +-
 .../java/org/apache/jackrabbit/spi/Helper.java     |    2 +-
 jackrabbit-spi2dav/pom.xml                         |   27 +-
 .../org/apache/jackrabbit/spi2dav/BatchUtils.java  |   79 ++
 .../apache/jackrabbit/spi2dav/DocumentTree.java    |   94 ++
 .../org/apache/jackrabbit/spi2dav/EventImpl.java   |   13 +-
 .../org/apache/jackrabbit/spi2dav/IdURICache.java  |   32 +-
 .../apache/jackrabbit/spi2dav/LockInfoImpl.java    |   19 +-
 .../apache/jackrabbit/spi2dav/QueryInfoImpl.java   |   72 +-
 .../jackrabbit/spi2dav/QueryResultRowImpl.java     |   45 +-
 .../jackrabbit/spi2dav/RepositoryServiceImpl.java  |  471 +++++--
 .../apache/jackrabbit/spi2dav/SessionInfoImpl.java |    9 +-
 .../apache/jackrabbit/spi2dav/URIResolverImpl.java |   29 +-
 .../jackrabbit/spi2davex/QValueFactoryImpl.java    |    2 +-
 .../spi2davex/RepositoryServiceImpl.java           |  314 +++--
 .../Spi2davexRepositoryServiceFactory.java         |   16 +-
 .../org/apache/jackrabbit/spi2davex/Utils.java     |  115 ++
 .../apache/jackrabbit/spi2davex/ValueLoader.java   |    6 +-
 .../jackrabbit/spi2dav/RepositoryStubImpl.java     |    2 +-
 .../test/resources/repositoryStubImpl.properties   |    8 +-
 jackrabbit-spi2jcr/pom.xml                         |   40 +-
 .../apache/jackrabbit/spi2jcr/QueryInfoImpl.java   |   27 +-
 .../jackrabbit/spi2jcr/QueryResultRowImpl.java     |   20 +-
 .../jackrabbit/spi2jcr/RepositoryServiceImpl.java  |  120 ++
 .../org/apache/jackrabbit/spi2jcr/XmlTree.java     |   95 ++
 .../jackrabbit/spi2jcr/RepositoryStubImpl.java     |   13 +-
 .../src/test/resources/repository.xml              |   11 +-
 jackrabbit-standalone/pom.xml                      |    8 +-
 .../src/main/appended-resources/META-INF/LICENSE   |  529 +++++++-
 .../standalone/cli/ext/ConnectToJNDIServer.java    |  142 +-
 jackrabbit-webapp/README.txt                       |    8 +-
 jackrabbit-webapp/pom.xml                          |   43 +-
 .../java/org/apache/jackrabbit/j2ee/Installer.java |   43 +-
 .../jackrabbit/j2ee/JcrApiNotFoundException.java   |   76 +-
 .../jackrabbit/j2ee/RepositoryAccessServlet.java   |   10 +-
 .../jackrabbit/j2ee/RepositoryStartupServlet.java  |   89 +-
 jackrabbit-webapp/src/main/webapp/META-INF/LICENSE |  739 ++++++++++-
 .../webapp/WEB-INF/protectedHandlers.properties    |   17 +
 jackrabbit-webapp/src/main/webapp/WEB-INF/web.xml  |    5 +
 .../src/main/webapp/bootstrap/missing.jsp          |    9 +
 .../src/main/webapp/error/classpath.jsp            |   56 +-
 .../src/main/webapp/error/repository.jsp           |   82 +-
 jackrabbit-webapp/src/main/webapp/footer.jsp       |   52 +-
 jackrabbit-webapp/src/main/webapp/header.jsp       |  178 +--
 jackrabbit-webapp/src/main/webapp/local.jsp        |  206 +--
 jackrabbit-webapp/src/main/webapp/populate.jsp     |   61 +-
 jackrabbit-webapp/src/main/webapp/remote.jsp       |  212 +--
 .../src/main/webapp/troubleshooting.jsp            |  210 +--
 jackrabbit-webapp/src/main/webapp/webdav-jcr.jsp   |  175 +--
 .../src/main/webapp/webdav-remoting.jsp            |    3 +-
 .../src/main/webapp/webdav-simple.jsp              |    3 +-
 .../jackrabbit/j2ee/BackwardsCompatibilityIT.java  |  243 ++++
 .../java/org/apache/jackrabbit/j2ee/TomcatIT.java  |   61 +-
 .../src/test/resources/compatibility.zip           |  Bin 0 -> 5589219 bytes
 .../src/test/resources/default-web.xml             | 1205 -----------------
 .../src/test/resources/logback-test.xml            |    2 +-
 jackrabbit-webdav/pom.xml                          |    2 +-
 .../jackrabbit/webdav/AbstractLocatorFactory.java  |    4 +-
 .../org/apache/jackrabbit/webdav/DavConstants.java |    2 +-
 .../org/apache/jackrabbit/webdav/DavException.java |    4 +-
 .../jackrabbit/webdav/MultiStatusResponse.java     |    2 +-
 .../jackrabbit/webdav/WebdavRequestImpl.java       |   13 +-
 .../jackrabbit/webdav/bind/BindServletRequest.java |  130 +-
 .../webdav/client/methods/RebindMethod.java        |  108 +-
 .../apache/jackrabbit/webdav/io/package-info.java  |    2 +-
 .../jackrabbit/webdav/lock/AbstractActiveLock.java |    4 +-
 .../apache/jackrabbit/webdav/lock/ActiveLock.java  |    4 +-
 .../webdav/observation/ObservationConstants.java   |   15 +-
 .../org/apache/jackrabbit/webdav/package-info.java |    2 +-
 .../webdav/property/DefaultDavProperty.java        |   12 +-
 .../jackrabbit/webdav/property/PropContainer.java  |    4 +-
 .../jackrabbit/webdav/search/SearchInfo.java       |    2 +-
 .../webdav/security/SupportedPrivilege.java        |   56 +
 .../security/SupportedPrivilegeSetProperty.java    |   28 +
 .../webdav/security/report/AclPrincipalReport.java |    2 +-
 .../security/report/PrincipalMatchReport.java      |    2 +-
 .../security/report/PrincipalSearchReport.java     |    6 +-
 .../security/report/SearchablePropertyReport.java  |    4 +-
 .../webdav/server/AbstractWebdavServlet.java       |   32 +-
 .../apache/jackrabbit/webdav/util/EncodeUtil.java  |    2 +-
 .../webdav/util/LinkHeaderFieldParser.java         |  398 +++---
 .../jackrabbit/webdav/util/package-info.java       |    2 +-
 .../webdav/version/ActivityResource.java           |    6 +-
 .../webdav/version/BaselineResource.java           |    4 +-
 .../jackrabbit/webdav/version/DeltaVConstants.java |    4 +-
 .../jackrabbit/webdav/version/DeltaVResource.java  |    4 +-
 .../webdav/version/VersionControlledResource.java  |    8 +-
 .../webdav/version/VersionHistoryResource.java     |    4 +-
 .../jackrabbit/webdav/version/VersionResource.java |    8 +-
 .../webdav/version/VersionableResource.java        |    4 +-
 .../webdav/version/WorkspaceResource.java          |    8 +-
 .../version/report/ExpandPropertyReport.java       |    2 +-
 .../webdav/xml/DavDocumentBuilderFactory.java      |   87 ++
 .../org/apache/jackrabbit/webdav/xml/DomUtil.java  |   54 +-
 .../apache/jackrabbit/webdav/xml/package-info.java |    2 +-
 .../server/RFC4918DestinationHeaderTest.java       |  240 ++--
 .../webdav/util/LinkHeaderFieldParserTest.java     |  132 +-
 .../apache/jackrabbit/webdav/xml/ParserTest.java   |  143 ++
 .../org/apache/jackrabbit/webdav/xml/TestAll.java  |    1 +
 pom.xml                                            |   25 +-
 test/compatibility/README.txt                      |   10 +-
 test/compatibility/assembly.xml                    |    8 +
 test/compatibility/create24/pom.xml                |   64 +
 .../compatibility/CreateRepositoryTest.java        |   28 +
 test/compatibility/create26/pom.xml                |   64 +
 .../compatibility/CreateRepositoryTest.java        |   28 +
 test/compatibility/pom.xml                         |    2 +
 test/performance/base/pom.xml                      |    2 +-
 .../performance/AbstractDeepTreeTest.java          |  105 ++
 .../ConcurrentReadAccessControlledTreeTest.java    |  109 ++
 .../performance/ConcurrentReadDeepTreeTest.java    |   63 +
 .../jackrabbit/performance/ReadDeepTreeTest.java   |   45 +
 .../base/src/main/resources/deepTree.xml           |    1 +
 test/performance/jackrabbit26/pom.xml              |   58 +
 .../jackrabbit/performance/PerformanceTest.java    |   32 +
 .../resources/btree-usermanager-repository.xml     |  159 +++
 .../resources/default-usermanager-repository.xml   |  156 +++
 test/performance/pom.xml                           |    5 +-
 1237 files changed, 55778 insertions(+), 21657 deletions(-)

diff --git a/NOTICE.txt b/NOTICE.txt
index 364635b..0dc6f68 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -1,5 +1,5 @@
 Apache Jackrabbit
-Copyright 2011 The Apache Software Foundation
+Copyright 2014 The Apache Software Foundation
 
 This product includes software developed at
 The Apache Software Foundation (http://www.apache.org/).
diff --git a/README.txt b/README.txt
index cec0185..4a6c6e3 100644
--- a/README.txt
+++ b/README.txt
@@ -19,15 +19,15 @@ You can build Jackrabbit like this:
 
     mvn clean install
 
-You need Maven 2.0.9 (or higher) with Java 5 (or higher) for the build.
-For more instructions, please see the documentation at:
+You need Maven 2.1 (or higher, Maven 3 recommended) with Java 6 (or higher)
+for the build. For more instructions, please see the documentation at:
 
    http://jackrabbit.apache.org/building-jackrabbit.html
 
 License (see also LICENSE.txt)
 ==============================
 
-Collective work: Copyright 2011 The Apache Software Foundation.
+Collective work: Copyright 2014 The Apache Software Foundation.
 
 Licensed to the Apache Software Foundation (ASF) under one or more
 contributor license agreements.  See the NOTICE file distributed with
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 70b6855..6b8a387 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -1,167 +1,69 @@
-Release Notes -- Apache Jackrabbit -- Version 2.3.6
+Release Notes -- Apache Jackrabbit -- Version 2.10.1
 
 Introduction
 ------------
 
-This is Apache Jackrabbit(TM) 2.3, a fully compliant implementation of the
+This is Apache Jackrabbit(TM) 2.10.1, a fully compliant implementation of the
 Content Repository for Java(TM) Technology API, version 2.0 (JCR 2.0) as
 specified in the Java Specification Request 283 (JSR 283).
 
-Apache Jackrabbit 2.3 is an unstable series of releases cut directly from
-Jackrabbit trunk, with a focus on new features and other improvements.
-For production use we recommend the latest stable 2.2 release.
+Apache Jackrabbit 2.10.1 is a patch release that contains fixes and
+improvements over Jackrabbit 2.10. Jackrabbit 2.10.x releases are considered
+stable and targeted for production use.
 
-Changes in Jackrabbit 2.3.6
----------------------------
+Security advisory (JCR-3883 / CVE-2015-1833)
+--------------------------------------------
 
-New features
+This release fixes an important security issue in the jackrabbit-webdav module
+reported by Mikhail Egorov.
 
-  [JCR-3005] Make it possible to get multiple nodes in one call via davex
-  [JCR-3183] Add memory based bundle store
+When processing a WebDAV request body containing XML, the XML parser can be 
+instructed to read content from network resources accessible to the host, 
+identified by URI schemes such as "http(s)" or  "file". Depending on the 
+WebDAV request, this can not only be used to trigger internal network 
+requests, but might also be used to insert said content into the request, 
+potentially exposing it to the attacker and others (for instance, by inserting
+said content in a WebDAV property value using a PROPPATCH request). See also
+IETF RFC 4918, Section 20.6.
 
-Improvements
+Users of the jackrabbit-webdav module are advised to immediately update the
+module to this release or disable WebDAV access to the repository. Users
+on earlier versions of Jackrabbit who are unable to upgrade to 2.10.1 should
+apply the fix to the corresponding 2.x branch or disable WebDAV access until
+official releases of those earlier versions are available. Patches for 2.x
+branches are attached to the JIRA issue.
 
-  [JCR-3162] Index update overhead on cluster slave due to JCR-905
-  [JCR-3172] implement PERSIST events for the EventJournal
-  [JCR-3177] Remove jdk 1.4 restriction for jcr-tests
-  [JCR-3178] Improve error messages for index aggregates
+Changes since Jackrabbit 2.10.0
+-------------------------------
 
 Bug fixes
 
-  [JCR-2541] spi2dav : EventJournal not  implemented
-  [JCR-2930] same named child nodes disappear on restore
-  [JCR-3174] Destination URI should be normalized
-  [JCR-3175] InputContextImpl: cannot upload file larger than 2GB
-  [JCR-3176] JCARepositoryManager does not close InputStream
-
-Changes in Jackrabbit 2.3.5
----------------------------
+  [JCR-3853] JCR2SPI: Load ac provider resource
+  [JCR-3871] POI Vulnerabilities
+  [JCR-3872] Config DTD does not declare ProtectedItemImporter elements
+  [JCR-3873] CachingDataStore not safe against crashes, corrupted uploads file will prevent system startup
+  [JCR-3876] POM dependency to jackrabbit-data test-jar is not test-scoped 
+  [JCR-3878] Fix test case failure in jackrabbit-data
+  [JCR-3883] Jackrabbit WebDAV bundle susceptible to XXE/XEE attack
 
 Improvements
 
-  [JCR-2887] Split PrivilegeRegistry in a per-session manager instance ...
-  [JCR-2906] Multivalued property sorted by last/random value
-  [JCR-3138] Skip sync delay when changes are found
-  [JCR-3161] Add JcrUtils.getPropertyTypeNames
-  [JCR-3165] Consolidate compare behaviour for Value(s) and Comparable(s)
-  [JCR-3167] Make Jackrabbit compile on Java 7
-  [JCR-3170] Precompile JavaCC parsers in jackrabbit-spi-commons
-
-Bug fixes
-
-  [JCR-3159] LOWER operand with nested LOCALNAME operand not work with SQL2
-  [JCR-3160] Session#move doesn't trigger rebuild of parent node aggregation
-  [JCR-3163] NPE in RepositoryServiceImpl.getPropertyInfo()
+  [JCR-3864] CachingDatastore -cache file sizes to save remote call to remote datastore( S3DS) 
+  [JCR-3868] Adapt TestCaseBase.java to test for FileDatastore
+  [JCR-3869] CachingDataStore for SAN or NFS mounted storage 
+  [JCR-3879] Remove contention in AsyncUploadCache to improve performance
+  [JCR-3881] Change CachingFDS configuration properties 
 
-Changes in Jackrabbit 2.3.4
----------------------------
+New Features
 
-New features
+  [JCR-3836] Allow to get an Authorizable of a given type 
 
-  [JCR-2936] JMX Bindings for Jackrabbit
-  [JCR-3040] JMX Stats for the Session
-  [JCR-3140] Add configurable hook for password validation
-  [JCR-3154] Stats for Queries continued
-
-Improvements
-
-  [JCR-3129] It should be possible to create a non-transient Repository ...
-  [JCR-3133] Query Stats should use the TimeSeries mechanism
-  [JCR-3142] Create OSGi Bundles from jackrabbit-webdav and ...
-  [JCR-3143] SessionImpl#isSupportedOption: Skip descriptor evaluation ...
-  [JCR-3146] Text extraction may congest thread pool in the repository
-
-Bug fixes
-
-  [JCR-2539] spi2dav: Observation's user data not property handled
-  [JCR-2540] spi2dav : move/reorder not properly handled by observation
-  [JCR-2542] spi2dav: EventFilters not respected
-  [JCR-3148] Using transactions still leads to memory leak
-  [JCR-3149] AccessControlProvider#getEffectivePolicies for a set of ...
-  [JCR-3151] SharedFieldCache can cause a memory leak
-  [JCR-3152] AccessControlImporter does not import repo level ac content
-  [JCR-3156] Group#getMembers may list inherited members multiple times
-
-Changes in Jackrabbit 2.3.3
----------------------------
-
-New features
-
-  [JCR-3118] Configurable actions upon authorizable creation and removal
-
-Improvements
-
-  [JCR-1443] ake JCAManagedConnectionFactory non final, so it can be extended
-  [JCR-2798] JCAManagedConnectionFactory should chain cause exception
-  [JCR-3120] Change log level in UserManagerImpl#getAuthorizable(NodeImpl) ...
-  [JCR-3127] Upgrade to Tika 0.10
-  [JCR-3132] Test tooling updates
-  [JCR-3135] Upgrade to Logback 1.0
-  [JCR-3136] Add m2e lifecycle mappings for Eclipse Indigo
-  [JCR-3141] Upgrade to Tika 1.0
-
-Bug fixes
+Sub-tasks
 
-  [JCR-3093] Inconsistency between Session.getProperty and Node....
-  [JCR-3110] QNodeTypeDefinitionImpl.getSerializablePropertyDefs() ...
-  [JCR-3116] Cluster Node ID should be trimmed
-  [JCR-3131] NPE in ItemManager when calling Session.save() with nothing ...
-  [JCR-3139] missing sync in InternalVersionManagerImpl.externalUpdate ...
-
-Changes in Jackrabbit 2.3.2
----------------------------
-
-New features
-
-  [JCR-3117] Stats for the PersistenceManager
-  [JCR-3124] Stats for Queries
-
-Improvements
-
-  [JCR-2989] Support for embedded index aggregates
-  [JCR-3098] Add hit miss statistics and logging to caches
-  [JCR-3107] Speed up hierarchy cache initialization
-  [JCR-3109] Move PersistenceManagerTest from o.a.j.core to o.a.j.core....
-  [JCR-3114] expose PM for versioning manager so that the consistency ...
-  [JCR-3119] Improve aggregate node indexing code
-  [JCR-3122] QueryObjectModelImpl should execute queries as SessionOperation(s)
-
-Bug fixes
-
-  [JCR-2892] - Large fetch sizes have potentially deleterious effects on ...
-  [JCR-3093] - Inconsistency between Session.getProperty and Node....
-  [JCR-3108] - SQL2 ISDESCENDANTNODE can throw BooleanQuery#...
-  [JCR-3111] - InternalVersionManagerBase; missing null check after getNode()
-  [JCR-3112] - NodeTypeDefDiff.PropDefDiff.init() constraints change check ...
-  [JCR-3115] - Versioning fixup leaves persistence in a state where the ...
-  [JCR-3126] - The CredentialsWrapper should use a empty String as userId ...
-  [JCR-3128] - Problem with formerly escaped JCR node names when upgrading ...
-
-Changes in Jackrabbit 2.3.1
----------------------------
-
-Improvements
-
-  [JCR-3017] Version history recovery fails in case a version does not ...
-  [JCR-3030] Permit using different tablespaces for tables and indexes ...
-  [JCR-3084] Script for checking releases
-  [JCR-3085] better diagnostics when version storage is broken
-  [JCR-3091] Lucene Scorer implementations should handle the 'advance' ...
-  [JCR-3102] InternalVersion.getFrozenNode confused about root version?
-
-Bug fixes
-
-  [JCR-2774] Access control for repository level API operations
-  [JCR-3082] occasional index out of bounds exception while running ...
-  [JCR-3086] potential infinite loop around InternalVersionImpl.getSuccessors
-  [JCR-3089] javax.jcr.RepositoryException when a JOIN SQL2 query is ...
-  [JCR-3090] setFetchSize() fails in getAllNodeIds()
-  [JCR-3095] Move operation may turn AC caches stale
-  [JCR-3101] recovery tool does not recover when version history can ...
-  [JCR-3105] NPE when versioning operations are concurrent
+  [JCR-3837] Add AuthorizableTypeException in user security API package
 
 In addition to the above-mentioned changes, this release contains
-all the changes included up to the Apache Jackrabbit 2.3.0 release.
+all the changes included up to the Apache Jackrabbit 2.10.0 release.
 
 For more detailed information about all the changes in this and other
 Jackrabbit releases, please see the Jackrabbit issue tracker at
@@ -195,10 +97,16 @@ About The Apache Software Foundation
 ------------------------------------
 
 Established in 1999, The Apache Software Foundation provides organizational,
-legal, and financial support for more than 100 freely-available,
+legal, and financial support for more than 140 freely-available,
 collaboratively-developed Open Source projects. The pragmatic Apache License
 enables individual and commercial users to easily deploy Apache software;
 the Foundation's intellectual property framework limits the legal exposure
-of its 2,500+ contributors.
+of its 3,800+ contributors.
 
 For more information, visit http://www.apache.org/
+
+Trademarks
+----------
+
+Apache Jackrabbit, Jackrabbit, Apache, the Apache feather logo, and the Apache
+Jackrabbit project logo are trademarks of The Apache Software Foundation.
diff --git a/check-release.sh b/check-release.sh
deleted file mode 100755
index 00d2212..0000000
--- a/check-release.sh
+++ /dev/null
@@ -1,115 +0,0 @@
-#!/bin/sh
-
-## 
-##    Licensed to the Apache Software Foundation (ASF) under one or more
-##    contributor license agreements.  See the NOTICE file distributed with
-##    this work for additional information regarding copyright ownership.
-##    The ASF licenses this file to You under the Apache License, Version 2.0
-##    (the "License"); you may not use this file except in compliance with
-##    the License.  You may obtain a copy of the License at
-## 
-##      http://www.apache.org/licenses/LICENSE-2.0
-## 
-##    Unless required by applicable law or agreed to in writing, software
-##    distributed under the License is distributed on an "AS IS" BASIS,
-##    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-##    See the License for the specific language governing permissions and
-##    limitations under the License.
-## 
-
-USERNAME=${1}
-VERSION=${2}
-SHA=${3}
-
-if [ -z "$USERNAME" -o -z "$VERSION" -o -z "$SHA" ]
-then
- echo "Usage: $0 <username> <version-number> <checksum> [temp-directory]"
- exit
-fi
-
-STAGING="http://people.apache.org/~$USERNAME/jackrabbit/$VERSION/"
-
-WORKDIR=${4:-target/jackrabbit-staging-`date +%s`}
-mkdir $WORKDIR -p -v
-
-echo "[INFO] ------------------------------------------------------------------------"
-echo "[INFO] DOWNLOAD STAGED REPOSITORY                                              "
-echo "[INFO] ------------------------------------------------------------------------"
-echo "[INFO] "
-
-if [ `wget --help | grep "no-check-certificate" | wc -l` -eq 1 ]
-then
-  CHECK_SSL=--no-check-certificate
-fi
-
-wget $CHECK_SSL --wait 1 -nv -r -np "--reject=html,txt" -P "$WORKDIR" -nH "--cut-dirs=3" --ignore-length "${STAGING}"
-
-echo "[INFO] ------------------------------------------------------------------------"
-echo "[INFO] CHECK SIGNATURES AND DIGESTS                                            "
-echo "[INFO] ------------------------------------------------------------------------"
-echo "[INFO] "
-
-## 1. check sha from release email against src.zip.sha file
-
-downloaded_sha=$(cat `find $WORKDIR -type f | grep jackrabbit-$VERSION-src.zip.sha`)
-if [ "$SHA" = "$downloaded_sha" ]; then echo "[INFO] Step 1. Release checksum matches provided checksum."; else echo "[ERROR] Step 1. Release checksum does not match provided checksum!"; fi
-echo "[INFO] "
-
-## 2. check signatures on the artifacts
-echo "[INFO] Step 2. Check individual files"
-
-for f in `find ${WORKDIR} -type f | grep '\.\(zip\|rar\|jar\|war\)$'`
-do
- echo "[INFO] $f"
- gpg --verify $f.asc 2>/dev/null
- if [ "$?" = "0" ]; then CHKSUM="GOOD"; else CHKSUM="BAD!!!!!!!!"; fi
- if [ ! -f "$f.asc" ]; then CHKSUM="----"; fi
- echo "gpg:  ${CHKSUM}"
-
- for hash in md5 sha1
- do
-   tp=`echo $hash | cut -c 1-3`
-   if [ ! -f "$f.$tp" ]
-   then
-     CHKSUM="----"
-   else
-     A="`cat $f.$tp 2>/dev/null`"
-     B="`openssl $hash < $f 2>/dev/null | sed 's/.*= *//' `"
-     if [ "$A" = "$B" ]; then CHKSUM="GOOD (`cat $f.$tp`)"; else CHKSUM="BAD!! : $A not equal to $B"; fi
-   fi
-   echo "$tp : ${CHKSUM}"
- done
-done
-
-## 3. check tag contents vs src archive contents
-echo "[INFO] "
-echo "[INFO] Step 3. Check SVN Tag for version $VERSION with src zip file contents"
-
-echo "[INFO] doing svn checkout, please wait..."
-SVNTAGDIR="$WORKDIR/tag-svn/jackrabbit-$VERSION"
-svn --quiet export http://svn.apache.org/repos/asf/jackrabbit/tags/$VERSION $SVNTAGDIR
-
-echo "[INFO] unzipping src zip file, please wait..."
-ZIPTAG="$WORKDIR/tag-zip"
-unzip -q $WORKDIR/jackrabbit-$VERSION-src.zip -d $ZIPTAG
-ZIPTAGDIR="$ZIPTAG/jackrabbit-$VERSION"
-
-DIFFOUT=`diff -r $SVNTAGDIR $ZIPTAGDIR`
-if [ -n "$DIFFOUT" ]
-then
- echo "[ERROR] Found some differences!"
- echo "$DIFFOUT"
-else
- echo "[INFO] No differences found."
-fi
-
-## 4. run the build with the pedantic profile to have the rat licence check enabled
-
-echo "[INFO] ------------------------------------------------------------------------"
-echo "[INFO] RUNNING MAVEN BUILD                                                     "
-echo "[INFO] ------------------------------------------------------------------------"
-echo "[INFO] "
-
-cd "$ZIPTAGDIR"
-mvn package -Ppedantic
-
diff --git a/examples/jackrabbit-firsthops/pom.xml b/examples/jackrabbit-firsthops/pom.xml
index 362254c..ab3d547 100644
--- a/examples/jackrabbit-firsthops/pom.xml
+++ b/examples/jackrabbit-firsthops/pom.xml
@@ -1,5 +1,3 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
 <!--
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
@@ -41,14 +39,28 @@
   <dependency>
    <groupId>org.apache.jackrabbit</groupId>
    <artifactId>jackrabbit-core</artifactId>
-   <version>2.2.4</version>
+   <version>2.9.0</version>
+  </dependency>
+
+  <!-- Jackrabbit Oak content repository -->
+  <!--
+    Comment out above jackrabbit-core and uncomment
+    below oak-jcr dependency if you want to run the
+    examples with Jackrabbit Oak.
+  -->
+  <!--
+  <dependency>
+    <groupId>org.apache.jackrabbit</groupId>
+    <artifactId>oak-jcr</artifactId>
+    <version>1.1.2</version>
   </dependency>
+  -->
 
   <!-- Use Log4J for logging -->
   <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
-   <version>1.5.11</version>
+   <version>1.7.5</version>
   </dependency>
 
  </dependencies>
diff --git a/examples/jackrabbit-firsthops/src/main/java/org/apache/jackrabbit/firsthops/FirstHop.java b/examples/jackrabbit-firsthops/src/main/java/org/apache/jackrabbit/firsthops/FirstHop.java
index 2364ee3..23ef4da 100644
--- a/examples/jackrabbit-firsthops/src/main/java/org/apache/jackrabbit/firsthops/FirstHop.java
+++ b/examples/jackrabbit-firsthops/src/main/java/org/apache/jackrabbit/firsthops/FirstHop.java
@@ -16,10 +16,11 @@
  */
 package org.apache.jackrabbit.firsthops;
 
+import javax.jcr.GuestCredentials;
 import javax.jcr.Repository;
 import javax.jcr.Session;
 
-import org.apache.jackrabbit.core.TransientRepository;
+import org.apache.jackrabbit.commons.JcrUtils;
 
 /**
  * First hop example. Logs in to a content repository and prints a status
@@ -36,8 +37,8 @@ public class FirstHop {
      *             if an error occurs
      */
     public static void main(String[] args) throws Exception {
-        Repository repository = new TransientRepository();
-        Session session = repository.login();
+        Repository repository = JcrUtils.getRepository();
+        Session session = repository.login(new GuestCredentials());
         try {
             String user = session.getUserID();
             String name = repository.getDescriptor(Repository.REP_NAME_DESC);
diff --git a/examples/jackrabbit-firsthops/src/main/java/org/apache/jackrabbit/firsthops/SecondHop.java b/examples/jackrabbit-firsthops/src/main/java/org/apache/jackrabbit/firsthops/SecondHop.java
index d700341..61a3bde 100644
--- a/examples/jackrabbit-firsthops/src/main/java/org/apache/jackrabbit/firsthops/SecondHop.java
+++ b/examples/jackrabbit-firsthops/src/main/java/org/apache/jackrabbit/firsthops/SecondHop.java
@@ -20,7 +20,8 @@ import javax.jcr.Repository;
 import javax.jcr.Session;
 import javax.jcr.SimpleCredentials;
 import javax.jcr.Node;
-import org.apache.jackrabbit.core.TransientRepository;
+
+import org.apache.jackrabbit.commons.JcrUtils;
 
 /**
  * Second hop example. Stores, retrieves, and removes example content.
@@ -36,9 +37,9 @@ public class SecondHop {
      *             if an error occurs
      */
     public static void main(String[] args) throws Exception {
-        Repository repository = new TransientRepository();
-        Session session = repository.login(new SimpleCredentials("username",
-                "password".toCharArray()));
+        Repository repository = JcrUtils.getRepository();
+        Session session = repository.login(new SimpleCredentials("admin",
+                "admin".toCharArray()));
         try {
             Node root = session.getRootNode();
 
diff --git a/examples/jackrabbit-firsthops/src/main/java/org/apache/jackrabbit/firsthops/ThirdHop.java b/examples/jackrabbit-firsthops/src/main/java/org/apache/jackrabbit/firsthops/ThirdHop.java
index 98b64e4..79b20f9 100644
--- a/examples/jackrabbit-firsthops/src/main/java/org/apache/jackrabbit/firsthops/ThirdHop.java
+++ b/examples/jackrabbit-firsthops/src/main/java/org/apache/jackrabbit/firsthops/ThirdHop.java
@@ -17,9 +17,10 @@
 package org.apache.jackrabbit.firsthops;
 
 import javax.jcr.*;
-import org.apache.jackrabbit.core.TransientRepository;
 import java.io.FileInputStream;
 
+import org.apache.jackrabbit.commons.JcrUtils;
+
 /**
  * Third Jackrabbit example application. Imports an example XML file and outputs
  * the contents of the entire workspace.
@@ -35,9 +36,9 @@ public class ThirdHop {
      *             if an error occurs
      */
     public static void main(String[] args) throws Exception {
-        Repository repository = new TransientRepository();
-        Session session = repository.login(new SimpleCredentials("username",
-                "password".toCharArray()));
+        Repository repository = JcrUtils.getRepository();
+        Session session = repository.login(new SimpleCredentials("admin",
+                "admin".toCharArray()));
 
         FileInputStream xml = new FileInputStream("src/main/resources/test.xml");
         try {
diff --git a/jackrabbit-api/pom.xml b/jackrabbit-api/pom.xml
index ae4ffcc..fd67364 100644
--- a/jackrabbit-api/pom.xml
+++ b/jackrabbit-api/pom.xml
@@ -26,12 +26,12 @@
   <parent>
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>jackrabbit-parent</artifactId>
-    <version>2.3.6</version>
+    <version>2.10.1</version>
     <relativePath>../jackrabbit-parent/pom.xml</relativePath>
   </parent>
   <artifactId>jackrabbit-api</artifactId>
   <name>Apache Jackrabbit API</name>
-  <description>Jacrabbit-specific extensions to the JCR API</description>
+  <description>Jackrabbit-specific extensions to the JCR API</description>
   <packaging>bundle</packaging>
 
   <build>
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/JackrabbitRepository.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/JackrabbitRepository.java
index 48f0fe2..9a84715 100644
--- a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/JackrabbitRepository.java
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/JackrabbitRepository.java
@@ -16,7 +16,14 @@
  */
 package org.apache.jackrabbit.api;
 
+import java.util.Map;
+
+import javax.jcr.Credentials;
+import javax.jcr.LoginException;
+import javax.jcr.NoSuchWorkspaceException;
 import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
 
 /**
  * The Jackrabbit repository interface. This interface contains the
@@ -25,6 +32,52 @@ import javax.jcr.Repository;
 public interface JackrabbitRepository extends Repository {
 
     /**
+     * Key to a <code>boolean</code> descriptor. Returns <code>true</code> if
+     * and only if user management is supported.
+     */
+    public static final String OPTION_USER_MANAGEMENT_SUPPORTED = "option.user.management.supported";
+
+    /**
+     * Key to a <code>boolean</code> descriptor. Returns <code>true</code> if
+     * and only if principal management is supported.
+     */
+    public static final String OPTION_PRINCIPAL_MANAGEMENT_SUPPORTED = "option.principal.management.supported";
+
+    /**
+     * Key to a <code>boolean</code> descriptor. Returns <code>true</code> if
+     * and only if privilege management is supported.
+     */
+    public static final String OPTION_PRIVILEGE_MANAGEMENT_SUPPORTED = "option.privilege.management.supported";
+
+    /**
+     * Equivalent to {@code login(credentials, workspaceName)} except that the returned
+     * Session instance contains the given extra session attributes in addition to any
+     * included in the given Credentials instance. Attribute names from the credentials
+     * and the attribute map must not overlap. In case of an overlap implementation
+     * may throw an <code>RepositoryException</code>.
+     * <p>
+     * The attributes are implementation-specific and may affect the behavior of the returned
+     * session. Unlike credentials attributes, these separately passed session attributes
+     * are guaranteed not to affect the authentication of the client.
+     * <p>
+     * An implementation that does not support a particular session attribute is expected
+     * to ignore it and not make it available through the returned session. A client that
+     * depends on specific behavior defined by a particular attribute can check whether
+     * the returned session contains that attribute to verify whether the underlying
+     * repository implementation supports that feature.
+     *
+     * @param credentials the credentials of the user
+     * @param workspaceName the name of a workspace
+     * @param attributes implementation-specific session attributes
+     * @return a valid session for the user to access the repository
+     * @throws LoginException if authentication or authorization for the specified workspace fails
+     * @throws NoSuchWorkspaceException if the specified workspace is not recognized
+     * @throws RepositoryException if another error occurs
+     */
+    Session login(Credentials credentials, String workspaceName, Map<String, Object> attributes)
+            throws LoginException, NoSuchWorkspaceException, RepositoryException;
+
+    /**
      * Shuts down the repository. A Jackrabbit repository instance contains
      * a acquired resources and cached data that needs to be released and
      * persisted when the repository is no longer used. This method handles
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/ReferenceBinary.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/ReferenceBinary.java
new file mode 100644
index 0000000..b4378e2
--- /dev/null
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/ReferenceBinary.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.api;
+
+import javax.jcr.Binary;
+
+/**
+ * Referenceable binary. In addition to the normal JCR {@link Binary}
+ * functionality, implementations of this class contain a <em>secure
+ * reference</em> to the storage location of the binary stream. This
+ * reference can be used to efficiently copy binaries across servers as
+ * long as both the source and target servers use the same underlying
+ * storage for binaries.
+ */
+public interface ReferenceBinary extends Binary {
+
+    /**
+     * Returns a secure reference to this binary, or {@code null} if such
+     * a reference is not available.
+     *
+     * @return binary reference, or {@code null}
+     */
+    String getReference();
+
+}
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/ReferenceBinaryException.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/ReferenceBinaryException.java
new file mode 100644
index 0000000..4ae0f3b
--- /dev/null
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/ReferenceBinaryException.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.api;
+
+import javax.jcr.RepositoryException;
+
+public class ReferenceBinaryException extends RepositoryException {
+
+    public ReferenceBinaryException(String message) {
+        super(message);
+    }
+
+}
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/jmx/EventListenerMBean.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/jmx/EventListenerMBean.java
new file mode 100644
index 0000000..210a94d
--- /dev/null
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/jmx/EventListenerMBean.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.api.jmx;
+
+import javax.management.openmbean.CompositeData;
+
+/**
+ * MBean interface for exposing information about a registered observation
+ * listener.
+ *
+ * @see <a href="https://issues.apache.org/jira/browse/JCR-3608">JCR-3608</a>
+ */
+public interface EventListenerMBean {
+
+    /** Class name of the event listener */
+    String getClassName();
+
+    /** Stack trace of where the listener was registered */
+    String getInitStackTrace();
+
+    /** Event types of the listener registration */
+    int getEventTypes();
+
+    /** Absolute path of the listener registration */
+    String getAbsPath();
+
+    /** Whether the listener registration is deep */
+    boolean isDeep();
+
+    /** UUIDs of the listener registration */
+    String[] getUuid();
+
+    /** Node types of the listener registration */
+    String[] getNodeTypeName();
+
+    /** Whether the listener registration is non-local */
+    boolean isNoLocal();
+
+    /** Number of {@code onEvent()} calls made on the listener */
+    long getEventDeliveries();
+
+    /** Average number of {@code onEvent()} calls per hour */
+    long getEventDeliveriesPerHour();
+
+    /** Average time (in microseconds) taken per {@code onEvent()} call */
+    long getMicrosecondsPerEventDelivery();
+
+    /** Number of individual events delivered to the listener */
+    long getEventsDelivered();
+
+    /** Average number of individual events delivered per hour */
+    long getEventsDeliveredPerHour();
+
+    /** Average time (in microseconds) taken per event delivered */
+    long getMicrosecondsPerEventDelivered();
+
+    /** Ratio of time spent in event processing */
+    double getRatioOfTimeSpentProcessingEvents();
+
+    /** Is user information accessed without checking if an event is external? */
+    boolean isUserInfoAccessedWithoutExternalsCheck();
+
+    /** Is user information accessed from an external event? */
+    boolean isUserInfoAccessedFromExternalEvent();
+
+    /** Is date information accessed without checking if an event is external? */
+    boolean isDateAccessedWithoutExternalsCheck();
+
+    /** Is date information accessed from an external event? */
+    boolean isDateAccessedFromExternalEvent();
+
+    /**
+     * {@link org.apache.jackrabbit.api.stats.TimeSeries time series} of the number of
+     * items related to generating observation events that are currently queued by the
+     * system. The exact nature of these items is implementation specific and might not
+     * be in a one to one relation with the number of pending JCR events.
+     * @return  time series of the queue length
+     */
+    CompositeData getQueueLength();
+
+    /**
+     * @return  time series of the number of JCR events
+     */
+    CompositeData getEventCount();
+
+    /**
+     * @return  time series of the time it took an event listener to process JCR events.
+     */
+    CompositeData getEventConsumerTime();
+
+    /**
+     * @return  time series of the time it took the system to produce JCR events.
+     */
+    CompositeData getEventProducerTime();
+
+}
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/jmx/package-info.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/jmx/package-info.java
index 10e67bc..61c6ee9 100644
--- a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/jmx/package-info.java
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/jmx/package-info.java
@@ -18,5 +18,5 @@
 /**
  * JMX management interfaces for JCR.
  */
- at aQute.bnd.annotation.Version("2.0.0")
+ at aQute.bnd.annotation.Version("2.1.0")
 package org.apache.jackrabbit.api.jmx;
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/management/DataStoreGarbageCollector.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/management/DataStoreGarbageCollector.java
index 66306e8..3b5061d 100644
--- a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/management/DataStoreGarbageCollector.java
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/management/DataStoreGarbageCollector.java
@@ -19,17 +19,19 @@ package org.apache.jackrabbit.api.management;
 import javax.jcr.RepositoryException;
 
 /**
- * Garbage collector for DataStore. This implementation is iterates through all
+ * Garbage collector for DataStore. This implementation iterates through all
  * nodes and reads the binary properties. To detect nodes that are moved while
  * the scan runs, event listeners are started. Like the well known garbage
  * collection in Java, the items that are still in use are marked. Currently
- * this achieved by updating the modified date of the entries. Newly added
+ * this is achieved by updating the modified date of the entries. Newly added
  * entries are detected because the modified date is changed when they are
  * added.
  * <p>
  * Example code to run the data store garbage collection:
  * <pre>
- * DataStoreGarbageCollector gc = ((JackrabbitSession)session).createDataStoreGarbageCollector();
+ * JackrabbitRepositoryFactory jf = (JackrabbitRepositoryFactory) factory;
+ * RepositoryManager m = jf.getRepositoryManager((JackrabbitRepository) repository);
+ * GarbageCollector gc = m.createDataStoreGarbageCollector();
  * gc.mark();
  * gc.sweep();
  * </pre>
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/management/package-info.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/management/package-info.java
index 196d7b5..081df6b 100644
--- a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/management/package-info.java
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/management/package-info.java
@@ -18,5 +18,5 @@
 /**
  * Interfaces for managing a Jackrabbit repository.
  */
- at aQute.bnd.annotation.Version("2.3")
+ at aQute.bnd.annotation.Version("2.3.1")
 package org.apache.jackrabbit.api.management;
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/observation/JackrabbitEvent.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/observation/JackrabbitEvent.java
index 35e5bb6..a7e7a05 100644
--- a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/observation/JackrabbitEvent.java
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/observation/JackrabbitEvent.java
@@ -1,35 +1,35 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.api.observation;
-
-import javax.jcr.observation.Event;
-
-/**
- * This is an extension of the event interface which provides
- * a method to detect whether the changes happened on locally
- * or remotely in a clustered environment.
- */
-public interface JackrabbitEvent extends Event {
-
-    /**
-     * Return a flag indicating whether this is an externally generated event.
-     *
-     * @return <code>true</code> if this is an external event;
-     *         <code>false</code> otherwise
-     */
-    boolean isExternal();
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.api.observation;
+
+import javax.jcr.observation.Event;
+
+/**
+ * This is an extension of the event interface which provides
+ * a method to detect whether the changes happened on locally
+ * or remotely in a clustered environment.
+ */
+public interface JackrabbitEvent extends Event {
+
+    /**
+     * Return a flag indicating whether this is an externally generated event.
+     *
+     * @return <code>true</code> if this is an external event;
+     *         <code>false</code> otherwise
+     */
+    boolean isExternal();
+}
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/observation/JackrabbitEventFilter.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/observation/JackrabbitEventFilter.java
new file mode 100644
index 0000000..be63fbe
--- /dev/null
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/observation/JackrabbitEventFilter.java
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.api.observation;
+
+import static java.util.Arrays.copyOf;
+
+/**
+ * A storage object for event filter configuration.
+ * <p>
+ * The parameters of the filter can then be set by chaining the set methods,
+ * since each method returns the same <code>EventFilter</code> with the indicated parameter set.
+ * <p>
+ * Once the filter is configured, it and an {@link javax.jcr.observation.EventListener} object are
+ * passed to
+ * {@link org.apache.jackrabbit.api.observation.JackrabbitObservationManager#addEventListener(javax.jcr.observation.EventListener, JackrabbitEventFilter)}.
+ * <p>
+ * The filter restricts which events are sent to the <code>EventListener</code> according to the
+ * following parameters. Note that the term <i>associated parent node</i> of an event means the
+ * parent node of the item at (or formerly at) the path returned by
+ * {@link javax.jcr.observation.Event#getPath}.
+ * <ul>
+ * <li>
+ * <code>eventTypes</code>:
+ * A bitwise <code>OR</code> of the event types to be listened to. See
+ * {@link javax.jcr.observation.Event} for details.
+ * </li>
+ * <li>
+ * <code>absPath</code>, <code>absPaths</code>, <code>excludedPaths</code>,
+ * <code>isDeep</code>: Only events whose associated parent node is at one
+ * of the paths in <code>absPath</code> or <code>absPaths</code> (or within
+ * its subgraph, if <code>isDeep</code> is <code>true</code>) will be received
+ * except if the associated parent node is at one of the paths in
+ * <code>excludedPaths</code> or its subgraph.
+ * It is permissible to register a listener for a path where no node currently
+ * exists.
+ * </li>
+ * <li>
+ * <code>uuid</code>:
+ * Only events whose associated parent node has one of
+ * the identifiers in this list will be received. If his parameter is
+ * <code>null</code> then no identifier-related restriction is placed on
+ * events received. Note that specifying an empty array instead of
+ * <code>null</code> would result in no nodes being listened to. The term
+ * "UUID" is used for compatibility with JCR 1.0.
+ * </li>
+ * <li>
+ * <code>nodeTypeName</code>:
+ * Only events whose associated parent node has
+ * one of the node types (or a subtype of one of the node types) in this
+ * list will be received. If his parameter is <code>null</code> then no node
+ * type-related restriction is placed on events received. Note that
+ * specifying an empty array instead of <code>null</code> would result in no
+ * nodes types being listened to.
+ * </li>
+ * <li>
+ * <code>noLocal</code>: if <code>true</code>, then events
+ * generated by the session through which the listener was registered are
+ * ignored. Otherwise, they are not ignored.
+ * </li>
+ * <li>
+ * <code>noExternal</code>: if <code>true</code>, then events
+ * from external cluster nodes are ignored. Otherwise, they are not ignored.
+ * </li>
+ * <li>
+ * <code>noInternal</code>: if <code>true</code>, then events
+ * from this cluster node are ignored. Otherwise, they are not ignored.
+ * </li>
+ * </ul>
+ * The restrictions are "ANDed" together. In other words, for a particular node to be "listened to" it
+ * must meet all the restrictions.
+ *
+ */
+public class JackrabbitEventFilter {  // TODO extends EventFilter once JCR 2.1 is out
+    private int eventTypes;
+    private String absPath;
+    private boolean isDeep;
+    private String[] identifiers;
+    private String[] nodeTypeNames;
+    private boolean noLocal;
+    private String[] absPaths = new String[]{};
+    private String[] excludedPaths = new String[]{};
+    private boolean noExternal;
+    private boolean noInternal;
+
+    /**
+     * Sets the <code>eventTypes</code> parameter of the filter.
+     * If left unset, this parameter defaults to <code>0</code>.
+     *
+     * @param eventTypes an <code>int</code>.
+     * @return This EventFilter object with the <code>eventTypes</code> parameter set.
+     */
+    public JackrabbitEventFilter setEventTypes(int eventTypes) {
+        this.eventTypes = eventTypes;
+        return this;
+    }
+
+    /**
+     * Returns the <code>eventTypes</code> parameter of the filter.
+     *
+     * @return an <code>int</code>.
+     */
+    public int getEventTypes() {
+        return eventTypes;
+    }
+
+    /**
+     * Sets the <code>absPath</code> parameter of the filter.
+     * If left unset, this parameter defaults to <code>null</code>.
+     *
+     * @param absPath an absolute path <code>String</code>.
+     * @return This EventFilter object with the <code>absPath</code> parameter set.
+     */
+    public JackrabbitEventFilter setAbsPath(String absPath) {
+        this.absPath = absPath;
+        return this;
+    }
+
+    /**
+     * Returns the <code>absPath</code> parameter of the filter.
+     *
+     * @return a <code>String</code>.
+     */
+    public String getAbsPath() {
+        return absPath;
+    }
+
+    /**
+     * Sets the <code>isDeep</code> parameter of the filter.
+     * If left unset, this parameter defaults to <code>false</code>.
+     *
+     * @param isDeep a <code>boolean</code>.
+     * @return This EventFilter object with the <code>isDeep</code> parameter set.
+     */
+    public JackrabbitEventFilter setIsDeep(boolean isDeep) {
+        this.isDeep = isDeep;
+        return this;
+    }
+
+    /**
+     * Returns the <code>isDeep</code> parameter of the filter.
+     *
+     * @return a <code>boolean</code>.
+     */
+    public boolean getIsDeep() {
+        return isDeep;
+    }
+
+    /**
+     * Sets the <code>identifiers</code> parameter of the filter.
+     * If left unset, this parameter defaults to <code>null</code>.
+     *
+     * @param identifiers a <code>String</code> array.
+     * @return This EventFilter object with the <code>identifiers</code> parameter set.
+     */
+    public JackrabbitEventFilter setIdentifiers(String[] identifiers) {
+        this.identifiers = copyOf(identifiers, identifiers.length);
+        return null;
+    }
+
+    /**
+     * Returns the <code>uuids</code> parameter of the filter.
+     *
+     * @return a <code>String</code> array.
+     */
+    public String[] getIdentifiers() {
+        return identifiers == null ? null : copyOf(identifiers, identifiers.length);
+    }
+
+    /**
+     * Sets the <code>nodeTypeNames</code> parameter of the filter.
+     * If left unset, this parameter defaults to <code>null</code>.
+     *
+     * @param nodeTypeNames a <code>String</code> array.
+     * @return This EventFilter object with the <code>nodeTypes</code> parameter set.
+     */
+    public JackrabbitEventFilter setNodeTypes(String[] nodeTypeNames) {
+        this.nodeTypeNames = copyOf(nodeTypeNames, nodeTypeNames.length);
+        return this;
+    }
+
+    /**
+     * Returns the <code>nodeTypeName</code> parameter of the filter.
+     *
+     * @return a <code>String</code> array.
+     */
+    public String[] getNodeTypes() {
+        return nodeTypeNames == null ? null : copyOf(nodeTypeNames, nodeTypeNames.length);
+    }
+
+    /**
+     * Sets the <code>noLocal</code> parameter of the filter.
+     * If left unset, this parameter defaults to <code>false</code>.
+     *
+     * @param noLocal a <code>boolean</code>.
+     * @return This EventFilter object with the <code>noLocal</code> parameter set.
+     */
+    public JackrabbitEventFilter setNoLocal(boolean noLocal) {
+        this.noLocal = noLocal;
+        return this;
+    }
+
+    /**
+     * Returns the <code>noLocal</code> parameter of the filter.
+     *
+     * @return a <code>boolean</code>.
+     */
+    public boolean getNoLocal() {
+        return noLocal;
+    }
+
+    /**
+     * Sets the <code>absPaths</code> parameter of the filter.
+     * If left unset, this parameter defaults to an empty array.
+     *
+     * @param absPaths an absolute path <code>String</code> array.
+     * @return This EventFilter object with the <code>absPaths</code> parameter set.
+     */
+    public JackrabbitEventFilter setAdditionalPaths(String... absPaths) {
+        this.absPaths = copyOf(absPaths, absPaths.length);
+        return this;
+    }
+
+    /**
+     * Returns the <code>absPaths</code> parameter of the filter.
+     *
+     * @return a <code>String</code> array.
+     */
+    public String[] getAdditionalPaths() {
+        return copyOf(absPaths, absPaths.length);
+    }
+
+    /**
+     * Sets the <code>excludedPaths</code> parameter of the filter.
+     * If left unset, this parameter defaults to an empty array.
+     *
+     * @param excludedPaths an absolute path <code>String</code> array.
+     * @return This EventFilter object with the <code>excludedPaths</code> parameter set.
+     */
+    public JackrabbitEventFilter setExcludedPaths(String... excludedPaths) {
+        this.excludedPaths = copyOf(excludedPaths, excludedPaths.length);
+        return this;
+    }
+
+    /**
+     * Returns the <code>excludedPaths</code> parameter of the filter.
+     *
+     * @return a <code>String</code> array.
+     */
+    public String[] getExcludedPaths() {
+        return copyOf(excludedPaths, excludedPaths.length);
+    }
+
+    /**
+     * Sets the <code>noExternal</code> parameter of the filter.
+     * If left unset, this parameter defaults to <code>false</code>.
+     *
+     * @param noExternal a <code>boolean</code>.
+     * @return This EventFilter object with the <code>noExternal</code> parameter set.
+     */
+    public JackrabbitEventFilter setNoExternal(boolean noExternal) {
+        this.noExternal = noExternal;
+        return this;
+    }
+
+    /**
+     * Returns the <code>noExternal</code> parameter of the filter.
+     *
+     * @return a <code>boolean</code>.
+     */
+    public boolean getNoExternal() {
+        return noExternal;
+    }
+
+    /**
+     * Sets the <code>noInternal</code> parameter of the filter.
+     * If left unset, this parameter defaults to <code>false</code>.
+     *
+     * @param noInternal a <code>boolean</code>.
+     * @return This EventFilter object with the <code>noExternal</code> parameter set.
+     */
+    public JackrabbitEventFilter setNoInternal(boolean noInternal) {
+        this.noInternal = noInternal;
+        return this;
+    }
+
+    /**
+     * Returns the <code>noInternal</code> parameter of the filter.
+     *
+     * @return a <code>boolean</code>.
+     */
+    public boolean getNoInternal() {
+        return noInternal;
+    }
+
+}
\ No newline at end of file
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/observation/JackrabbitObservationManager.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/observation/JackrabbitObservationManager.java
new file mode 100644
index 0000000..122ad5a
--- /dev/null
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/observation/JackrabbitObservationManager.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.api.observation;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.observation.EventListener;
+import javax.jcr.observation.ObservationManager;
+
+/**
+ * Jackrabbit specific extensions to {@link javax.jcr.observation.ObservationManager}.
+ */
+public interface JackrabbitObservationManager extends ObservationManager {
+
+    /**
+     * Adds an event listener that listens for the events specified
+     * by the passed {@link JackrabbitEventFilter}.
+     * <p>
+     * In addition to the <code>EventFilter</code>, the set of events reported
+     * will be further filtered by the access rights of the
+     * current <code>Session</code>.
+     * <p>
+     * See {@link JackrabbitEventFilter} for a description of the filtering parameters available.
+     * <p>
+     * The filter of an already-registered <code>EventListener</code> can be
+     * changed at runtime by re-registering the same <code>EventListener</code>
+     * object (i.e. the same actual Java object) with a new filter.
+     * The implementation must ensure that no events are lost during the changeover.
+     * <p>
+     * In addition to the filters placed on a listener above, the scope of
+     * observation support, in terms of which parts of a workspace are observable, may also
+     * be subject to implementation-specific restrictions. For example, in some
+     * repositories observation of changes in the <code>jcr:system</code>
+     * subgraph may not be supported.
+     *
+     * @param listener     an {@link EventListener} object.
+     * @param filter       an {@link JackrabbitEventFilter} object.
+     * @throws RepositoryException If an error occurs.
+     */
+    void addEventListener(EventListener listener, JackrabbitEventFilter filter)
+            throws RepositoryException;
+}
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/observation/package-info.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/observation/package-info.java
index db76c33..e719d24 100644
--- a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/observation/package-info.java
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/observation/package-info.java
@@ -18,5 +18,5 @@
 /**
  * Jackrabbit extensions for JCR observation.
  */
- at aQute.bnd.annotation.Version("2.3")
+ at aQute.bnd.annotation.Version("2.3.1")
 package org.apache.jackrabbit.api.observation;
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/query/JackrabbitQueryResult.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/query/JackrabbitQueryResult.java
new file mode 100644
index 0000000..101c4e9
--- /dev/null
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/query/JackrabbitQueryResult.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.api.query;
+
+import javax.jcr.query.QueryResult;
+
+/**
+ * The Jackrabbit query result interface. This interface contains the
+ * Jackrabbit-specific extensions to the JCR {@link QueryResult} interface.
+ *
+ * @since Jackrabbit 2.6
+ */
+public interface JackrabbitQueryResult extends QueryResult {
+
+    /**
+     * Returns the total number of hits. This is the number of results you
+     * would get without any limit or offset settings. This method may return
+     * <code>-1</code> if the total size is unknown.
+     *
+     * @return the total number of hits, or <code>-1</code>
+     */
+    int getTotalSize();
+
+}
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlEntry.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlEntry.java
index 498e27b..21d971c 100644
--- a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlEntry.java
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlEntry.java
@@ -18,6 +18,7 @@ package org.apache.jackrabbit.api.security;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
 import javax.jcr.security.AccessControlEntry;
 
 /**
@@ -43,13 +44,35 @@ public interface JackrabbitAccessControlEntry extends AccessControlEntry {
 
     /**
      * Return the value of the restriction with the specified name or
-     * <code>null</code> if no such restriction exists.
+     * <code>null</code> if no such restriction exists. In case the restriction
+     * with the specified name contains multiple value this method will call
+     * {@code ValueFormatException}.
      *
      * @param restrictionName The of the restriction as obtained through
      * {@link #getRestrictionNames()}.
      * @return value of the restriction with the specified name or
-     * <code>null</code> if no such restriction exists
+     * <code>null</code> if no such restriction exists.
+     * @throws ValueFormatException If the restriction with the specified name
+     * contains multiple values.
+     * @throws RepositoryException if an error occurs.
+     * @see {@link #getRestrictions(String)}
+     */
+    Value getRestriction(String restrictionName) throws ValueFormatException, RepositoryException;
+
+    /**
+     * Return the values of the restriction with the specified name or
+     * <code>null</code> if no such restriction exists. For restrictions that
+     * contain just a single value this method is expected to return an array
+     * with a single element even if the underlying implementation stored the
+     * restriction in single-value JCR property.
+     *
+     * @param restrictionName The of the restriction as obtained through
+     * {@link #getRestrictionNames()}.
+     * @return the values of the restriction with the specified name as an array
+     * or <code>null</code> if no such restriction exists. The array may contain
+     * zero, one or multiple values.
      * @throws RepositoryException if an error occurs.
+     * @see {@link #getRestriction(String)}
      */
-    Value getRestriction(String restrictionName) throws RepositoryException;
+    Value[] getRestrictions(String restrictionName) throws RepositoryException;
 }
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlList.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlList.java
index f988b12..9d08a40 100644
--- a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlList.java
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/JackrabbitAccessControlList.java
@@ -16,16 +16,16 @@
  */
 package org.apache.jackrabbit.api.security;
 
+import java.security.Principal;
+import java.util.Map;
 import javax.jcr.RepositoryException;
-import javax.jcr.Value;
 import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Value;
+import javax.jcr.security.AccessControlEntry;
 import javax.jcr.security.AccessControlException;
 import javax.jcr.security.AccessControlList;
 import javax.jcr.security.AccessControlPolicy;
 import javax.jcr.security.Privilege;
-import javax.jcr.security.AccessControlEntry;
-import java.security.Principal;
-import java.util.Map;
 
 /**
  * <code>JackrabbitAccessControlList</code> is an extension of the <code>AccessControlList</code>.
@@ -93,10 +93,10 @@ public interface JackrabbitAccessControlList extends JackrabbitAccessControlPoli
      * <code>principal</code>, the specified <code>privileges</code>, the
      * <code>isAllow</code> flag and an optional map containing additional
      * restrictions.
-     * <p/>
+     * <p>
      * This method returns <code>true</code> if this policy was modified,
      * <code>false</code> otherwise.
-     * <p/>
+     * <p>
      * An <code>AccessControlException</code> is thrown if any of the specified
      * parameters is invalid or if some other access control related exception occurs.
      * 
@@ -118,6 +118,40 @@ public interface JackrabbitAccessControlList extends JackrabbitAccessControlPoli
             throws AccessControlException, RepositoryException;
 
     /**
+     * Adds an access control entry to this policy consisting of the specified
+     * <code>principal</code>, the specified <code>privileges</code>, the
+     * <code>isAllow</code> flag and an optional map containing additional
+     * restrictions.
+     * <p>
+     * This method returns <code>true</code> if this policy was modified,
+     * <code>false</code> otherwise.
+     * <p>
+     * An <code>AccessControlException</code> is thrown if any of the specified
+     * parameters is invalid or if some other access control related exception occurs.
+     *
+     * @param principal the principal to add the entry for
+     * @param privileges the privileges to add
+     * @param isAllow if <code>true</code> if this is a positive (allow) entry
+     * @param restrictions A map of additional restrictions used to narrow the
+     * effect of the entry to be created. The map must map JCR names to a single
+     * {@link javax.jcr.Value} object.
+     * @param restrictions A map of additional multivalued restrictions used to narrow the
+     * effect of the entry to be created. The map must map JCR names to a
+     * {@link javax.jcr.Value} array.
+     * @return true if this policy has changed by incorporating the given entry;
+     * false otherwise.
+     * @throws AccessControlException If any of the given parameter is invalid
+     * or cannot be handled by the implementation.
+     * @throws RepositoryException If another error occurs.
+     * @see AccessControlList#addAccessControlEntry(Principal, Privilege[])
+     * @since 2.8
+     */
+    boolean addEntry(Principal principal, Privilege[] privileges,
+                     boolean isAllow, Map<String, Value> restrictions,
+                     Map<String, Value[]> mvRestrictions)
+            throws AccessControlException, RepositoryException;
+
+    /**
      * If the <code>AccessControlList</code> implementation supports
      * reordering of entries the specified <code>srcEntry</code> is inserted
      * at the position of the specified <code>destEntry</code>.<p/>
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/principal/PrincipalManager.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/principal/PrincipalManager.java
index 5120f5f..cfad491 100644
--- a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/principal/PrincipalManager.java
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/principal/PrincipalManager.java
@@ -24,14 +24,14 @@ import java.security.acl.Group;
  * all principals known to the repository. Each principal manager is bound to
  * a session and is restricted by the respective access control. The principal
  * manager in addition provides basic search facilities.
- * <p/>
+ * <p>
  * A <strong>{@link Principal}</strong> is an object used to connect
  * to any kind of security mechanism. Example for this are the
  * {@link javax.security.auth.spi.LoginModule login modules} that use principals
  * to process the login procedure. <br/>
  * A principal can be a member of a <strong>{@link Group}</strong>. A
  * group is a principal itself and can therefore be a member of a group again.
- * <p/>
+ * <p>
  * Please note the following security considerations that need to be respected
  * when implementing the PrincipalManager: All principals returned by this
  * manager as well as {@link Group#members()} must respect access restrictions
@@ -80,7 +80,9 @@ public interface PrincipalManager {
      * <code>PrincipalManager</code> has been built for.
      *
      * @param principalName the name of the principal to retrieve
-     * @return return the requested principal or <code>null</code> if not exists
+     * @return return the requested principal or <code>null</code> if a
+     * principal with the given name does not exist or is not accessible
+     * for the editing session.
      */
     Principal getPrincipal(String principalName);
 
@@ -134,7 +136,7 @@ public interface PrincipalManager {
     /**
      * Returns an iterator over all group principals for which the given
      * principal is either direct or indirect member of.
-     * <p/>
+     * <p>
      * Example:<br>
      * If Principal P is member of Group A, and Group A is member of
      * Group B, this method will return Principal A and Principal B.
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/Authorizable.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/Authorizable.java
index 9a3e141..6502ccf 100644
--- a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/Authorizable.java
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/Authorizable.java
@@ -28,24 +28,24 @@ import javax.jcr.Value;
  * {@link Group}. It provides access to the <code>Principal</code>s associated
  * with an <code>Authorizable</code> (see below) and allow to access and
  * modify additional properties such as e.g. full name, e-mail or address.
- * <p/>
- * <p/>
+ * <p>
+ * <p>
  * Please note the difference between <code>Authorizable</code> and
  * {@link java.security.Principal Principal}:<br>
  * An <code>Authorizable</code> is repository object that is neither associated
  * with nor depending from a particular <code>Session</code> and thus independent
  * of the login mechanisms creating <code>Session</code>s.<br>
- * <p/>
+ * <p>
  * On the other hand <code>Principal</code>s are representations of user
  * identities. In other words: each <code>Principal</code> within the set
  * associated with the Session's Subject upon login represents an identity for
  * that user. An the set of <code>Principal</code>s may differ between different
  * login mechanisms.<br>
- * <p/>
+ * <p>
  * Consequently an one-to-many relationship exists between Authorizable
  * and Principal (see also {@link #getPrincipal()}.
- * <p/>
- * <p/>
+ * <p>
+ * <p>
  * The interfaces derived from Authorizable are defined as follows:
  * <ul>
  * <li>{@link User}: defined to be an Authorizable that can be authenticated
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/AuthorizableTypeException.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/AuthorizableTypeException.java
new file mode 100644
index 0000000..a70c707
--- /dev/null
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/AuthorizableTypeException.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.api.security.user;
+
+import javax.jcr.RepositoryException;
+
+/**
+ * The {@code AuthorizableTypeException} signals an {@link Authorizable} type mismatch.
+ */
+public class AuthorizableTypeException extends RepositoryException {
+
+    public AuthorizableTypeException(String msg) {
+        super(msg);
+    }
+}
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/User.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/User.java
index 197ecf1..e02e3db 100644
--- a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/User.java
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/User.java
@@ -34,6 +34,11 @@ public interface User extends Authorizable {
     boolean isAdmin();
 
     /**
+     * @return true if the current user represents a system user.
+     */
+    boolean isSystemUser();
+
+    /**
      * Returns the internal <code>Credentials</code> representation for this
      * user. This method is expected to be used for validation during the
      * login process. However, the return value should neither be usable nor
@@ -76,7 +81,7 @@ public interface User extends Authorizable {
      *
      * @param reason String describing the reason for disable this user or
      * <code>null</code> if the user account should be enabled again.
-     * @throws RepositoryException
+     * @throws RepositoryException If an error occurs.
      */
     void disable(String reason) throws RepositoryException;
 
@@ -86,7 +91,7 @@ public interface User extends Authorizable {
      *
      * @return <code>true</code> if this user is disabled, <code>false</code>
      * otherwise.
-     * @throws RepositoryException
+     * @throws RepositoryException If an error occurs.
      */
     boolean isDisabled() throws RepositoryException;
 
@@ -96,7 +101,7 @@ public interface User extends Authorizable {
      * 
      * @return The reason specified upon disabling this user or <code>null</code>
      * if this user is not disabled.
-     * @throws RepositoryException
+     * @throws RepositoryException If an error occurs.
      */
     String getDisabledReason() throws RepositoryException;
 }
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/UserManager.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/UserManager.java
index 96c881f..b2c0752 100644
--- a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/UserManager.java
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/UserManager.java
@@ -23,6 +23,8 @@ import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.UnsupportedRepositoryOperationException;
 
+import aQute.bnd.annotation.ProviderType;
+
 /**
  * The <code>UserManager</code> provides access to and means to maintain
  * {@link Authorizable authorizable objects} i.e. {@link User users} and
@@ -37,6 +39,7 @@ import javax.jcr.UnsupportedRepositoryOperationException;
  * {@link Session#save()} operation; callers should be prepared to repeat them
  * in case this happens.
  */
+ at ProviderType
 public interface UserManager {
 
     /**
@@ -68,9 +71,21 @@ public interface UserManager {
     Authorizable getAuthorizable(String id) throws RepositoryException;
 
     /**
-     * Get the Authorizable by its main Principal.
+     * Get the Authorizable of a specific type by its id.
+     *
+     * @param id the user or group id.
+     * @param authorizableClass the class of the type of Authorizable required; must not be <code>null</code>.
+     * @param <T> the required Authorizable type.
+     * @return Authorizable or <code>null</code>, if not present.
+     * @throws AuthorizableTypeException If an authorizable exists but is not of the requested type.
+     * @throws RepositoryException If an error occurs
+     */
+    <T extends Authorizable> T getAuthorizable(String id, Class<T> authorizableClass) throws AuthorizableTypeException, RepositoryException;
+
+    /**
+     * Get the Authorizable by its Principal.
      *
-     * @param principal
+     * @param principal The principal of the authorizable to retrieve.
      * @return Authorizable or <code>null</code>, if not present.
      * @throws RepositoryException If an error occurs.
      */
@@ -93,14 +108,14 @@ public interface UserManager {
      * Returns all <code>Authorizable</code>s that have a
      * {@link Authorizable#getProperty(String) property} with the given relative
      * path (or name) that matches the specified value.
-     * <p/>
+     * <p>
      * If a relative path with more than one segment is specified only properties
      * exactly matching that patch will be returned. If, however, a name is
      * specified all properties that may be retrieved using
      * {@link Authorizable#getProperty(String)} will be searched for a match.
      *
      * @param relPath A relative property path or name.
-     * @param value
+     * @param value A string value to match.
      * @return All <code>Authorizable</code>s that have a property with the given
      * name exactly matching the given value.
      * @throws RepositoryException If an error occurs.
@@ -114,14 +129,14 @@ public interface UserManager {
      * path (or name) that matches the specified value. In contrast to
      * {@link #findAuthorizables(String, String)} the type of authorizable is
      * respected while executing the search.
-     * <p/>
+     * <p>
      * If a relative path with more than one segment is specified only properties
      * exactly matching that path will be returned. If, however, a name is
      * specified all properties that may be retrieved using
      * {@link Authorizable#getProperty(String)} will be searched for a match.
      *
      * @param relPath A relative property path or name.
-     * @param value
+     * @param value A string value to match.
      * @param searchType Any of the following constants:
      * <ul>
      * <li>{@link #SEARCH_TYPE_AUTHORIZABLE}</li>
@@ -149,7 +164,7 @@ public interface UserManager {
      * the specified userID is equal to the principal name and the intermediate
      * path is <code>null</code>.
      *
-     * @param userID The id of the new user.
+     * @param userID The ID of the new user.
      * @param password The initial password of this user.
      * @return The new <code>User</code>.
      * @throws AuthorizableExistsException in case the given userID is already
@@ -165,10 +180,12 @@ public interface UserManager {
      * Except for the <code>intermediatePath</code>, neither of the specified
      * parameters can be <code>null</code>.
      *
-     * @param userID
-     * @param password
-     * @param principal
-     * @param intermediatePath
+     * @param userID The ID of the new user.
+     * @param password The initial password of the new user.
+     * @param principal The principal of the new user.
+     * @param intermediatePath An optional intermediate path used to create the
+     * new user. If the intermediate path is <code>null</code> an internal,
+     * implementation specific structure will be used.
      * @return The new <code>User</code>.
      * @throws AuthorizableExistsException in case the given userID is already
      * in use or another Authorizable with the same principal name exists.
@@ -178,6 +195,31 @@ public interface UserManager {
     User createUser(String userID, String password, Principal principal,
                     String intermediatePath) throws AuthorizableExistsException, RepositoryException;
 
+
+    /**
+     * Create a new system user for the specified {@code userID}. The new authorizable
+     * is required to have the following characteristics:
+     *
+     * <ul>
+     *     <li>{@link org.apache.jackrabbit.api.security.user.User#isSystemUser()} returns {@code true}.</li>
+     *     <li>The system user doesn't have a password set and doesn't allow change the password.</li>
+     *     <li>The principal name is generated by the system; it may be the same as {@code userID}.</li>
+     *     <li>A given implementation may choose to keep system users in a dedicated
+     *     location and thus may impose restrictions on the {@code intermediatePath}.</li>
+     * </ul>
+     *
+     * @param userID A valid userID.
+     * @param intermediatePath An optional intermediate path to create the new
+     * system user. The implemenation may decide to reject intermediate paths
+     * if they violate an implementation specific requirement with respect to
+     * the location where systems users are being held. If the intermediate path
+     * is {@code null} an internal implementation specific structure will be used.
+     * @return The new system user.
+     * @throws AuthorizableExistsException if an Authorizable with this id already exists.
+     * @throws RepositoryException If another error occurs.
+     */
+    User createSystemUser(String userID, String intermediatePath) throws AuthorizableExistsException, RepositoryException;
+
     /**
      * Creates a Group for the given groupID, which must not be <code>null</code>.
      * <br>
@@ -185,7 +227,7 @@ public interface UserManager {
      * groupID is the name of the <code>Principal</code> the intermediate path
      * is <code>null</code>.
      *
-     * @param groupID The id of the new group; must not be <code>null</code>.
+     * @param groupID The ID of the new group; must not be <code>null</code>.
      * @return The new <code>Group</code>.
      * @throws AuthorizableExistsException in case the given groupID is already
      * in use or another {@link Authorizable} with the same
@@ -212,8 +254,10 @@ public interface UserManager {
      * Same as {@link #createGroup(String, Principal, String)} where the
      * name of the specified principal is used to create the group's ID. 
      *
-     * @param principal
-     * @param intermediatePath
+     * @param principal The principal associated with the new group.
+     * @param intermediatePath An optional intermediate path used to create the
+     * new group. If the intermediate path is <code>null</code> an internal,
+     * implementation specific structure will be used.
      * @return The new <code>Group</code>.
      * @throws AuthorizableExistsException in case the given principal is
      * already in use with another Authorizable.
@@ -227,8 +271,11 @@ public interface UserManager {
      * is not able to deal with the <code>intermediatePath</code> this parameter
      * should be ignored.
      *
-     * @param principal
-     * @param intermediatePath
+     * @param groupID The ID of the new group.
+     * @param principal The principal of the new group.
+     * @param intermediatePath An optional intermediate path used to create the
+     * new group. If the intermediate path is <code>null</code> an internal,
+     * implementation specific structure will be used.
      * @return The new <code>Group</code>.
      * @throws AuthorizableExistsException in case the given principal is already
      * in use with another Authorizable.
@@ -254,7 +301,7 @@ public interface UserManager {
 
     /**
      * Changes the auto save behavior of this <code>UserManager</code>.
-     * <p/>
+     * <p>
      * Note, that this shouldn't be allowed in cases where the associated session
      * is different from the original session accessing the user manager.
      *
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/package-info.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/package-info.java
index 7dfcdf8..21316bf 100644
--- a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/package-info.java
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/package-info.java
@@ -18,5 +18,5 @@
 /**
  * Jackrabbit extensions for user management.
  */
- at aQute.bnd.annotation.Version("2.3.2")
+ at aQute.bnd.annotation.Version("2.4.0")
 package org.apache.jackrabbit.api.security.user;
diff --git a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/stats/RepositoryStatistics.java b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/stats/RepositoryStatistics.java
index b831525..34b261b 100644
--- a/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/stats/RepositoryStatistics.java
+++ b/jackrabbit-api/src/main/java/org/apache/jackrabbit/api/stats/RepositoryStatistics.java
@@ -23,6 +23,11 @@ package org.apache.jackrabbit.api.stats;
  */
 public interface RepositoryStatistics {
 
+    /**
+     * The values of this enum determine the type of the time
+     * series returned by {@link #getTimeSeries(Type)}
+     * and link {@link #getTimeSeries(String, boolean)}.
+     */
     enum Type {
         BUNDLE_READ_COUNTER(true),
         BUNDLE_WRITE_COUNTER(true),
@@ -35,17 +40,87 @@ public interface RepositoryStatistics {
         BUNDLE_CACHE_MISS_AVERAGE(false),
         BUNDLE_COUNTER(true),
         BUNDLE_WS_SIZE_COUNTER(true),
+
+        /**
+         * Number of read accesses through any session.
+         */
         SESSION_READ_COUNTER(true),
+
+        /**
+         * Total time spent reading from sessions in nano seconds.
+         */
         SESSION_READ_DURATION(true),
+
+        /**
+         * Average time spent reading from sessions in nano seconds.
+         * This is the sum of all read durations divided by the number
+         * of reads in the respective time period.
+         */
         SESSION_READ_AVERAGE(false),
+
+        /**
+         * Number of write accesses through any session.
+         */
         SESSION_WRITE_COUNTER(true),
+
+        /**
+         * Total time spent writing to sessions in nano seconds.
+         */
         SESSION_WRITE_DURATION(true),
+
+        /**
+         * Average time spent writing to sessions in nano seconds.
+         * This is the sum of all write durations divided by the number
+         * of writes in the respective time period.
+         */
         SESSION_WRITE_AVERAGE(false),
+
+        /**
+         * Number of calls sessions that have been logged in.
+         */
         SESSION_LOGIN_COUNTER(true),
+
+        /**
+         * Number of currently logged in sessions.
+         */
         SESSION_COUNT(false),
+
+        /**
+         * Number of queries executed.
+         */
         QUERY_COUNT(true),
+
+        /**
+         * Total time spent evaluating queries in milli seconds.
+         */
         QUERY_DURATION(true),
-        QUERY_AVERAGE(true);
+
+        /**
+         * Average time spent evaluating queries in milli seconds.
+         * This is the sum of all query durations divided by the number
+         * of queries in the respective time period.
+         */
+        QUERY_AVERAGE(true),
+
+        /**
+         * Total number of observation {@code Event} instances delivered
+         * to all observation listeners.
+         */
+        OBSERVATION_EVENT_COUNTER(true),
+
+        /**
+         * Total time spent processing observation events by all observation
+         * listeners in nano seconds.
+         */
+        OBSERVATION_EVENT_DURATION(true),
+
+        /**
+         * Average time spent processing observation events by all observation
+         * listeners in nano seconds.
+         * This is the sum of all observation durations divided by the number
+         * of observation events in the respective time period.
+         */
+        OBSERVATION_EVENT_AVERAGE(true);
 
         private final boolean resetValueEachSecond;
 
@@ -53,10 +128,20 @@ public interface RepositoryStatistics {
             this.resetValueEachSecond = resetValueEachSecond;
         }
 
+        public static Type getType(String type) {
+            Type realType = null;
+            try {
+                realType = valueOf(type);
+            } catch (IllegalArgumentException ignore) {}
+            return realType;
+        }
+
         public boolean isResetValueEachSecond() {
             return resetValueEachSecond;
         }
     }
 
     TimeSeries getTimeSeries(Type type);
+
+    TimeSeries getTimeSeries(String type, boolean resetValueEachSecond);
 }
diff --git a/jackrabbit-aws-ext/README.txt b/jackrabbit-aws-ext/README.txt
new file mode 100644
index 0000000..864329c
--- /dev/null
+++ b/jackrabbit-aws-ext/README.txt
@@ -0,0 +1,28 @@
+====================================================
+Welcome to Jackrabbit Amazon WebServices Extension
+====================================================
+
+This is the Amazon Webservices Extension component of the Apache Jackrabbit project.
+This component contains S3 Datastore which stores binaries on Amazon S3 (http://aws.amazon.com/s3).
+
+====================================================
+Build Instructions
+====================================================
+To build the latest SNAPSHOT versions of all the components
+included here, run the following command with Maven 3:
+
+    mvn clean install
+
+To run testcases which stores in S3 bucket, please pass aws config file via system property. For e.g.
+
+    mvn clean install  -DargLine="-Dconfig=/opt/cq/aws.properties"
+
+Sample aws properties located at src/test/resources/aws.properties
+
+====================================================
+Configuration Instructions
+====================================================
+It require to configure aws.properties to configure S3 Datastore.
+    <DataStore class="org.apache.jackrabbit.aws.ext.ds.S3DataStore">
+        <param name="config" value="${rep.home}/aws.properties"/>
+    </DataStore>
diff --git a/jackrabbit-aws-ext/pom.xml b/jackrabbit-aws-ext/pom.xml
new file mode 100644
index 0000000..9f787a2
--- /dev/null
+++ b/jackrabbit-aws-ext/pom.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
+    license agreements. See the NOTICE file distributed with this work for additional
+    information regarding copyright ownership. The ASF licenses this file to
+    You under the Apache License, Version 2.0 (the "License"); you may not use
+    this file except in compliance with the License. You may obtain a copy of
+    the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
+    by applicable law or agreed to in writing, software distributed under the
+    License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+    OF ANY KIND, either express or implied. See the License for the specific
+    language governing permissions and limitations under the License. -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd ">
+    <modelVersion>4.0.0</modelVersion>
+
+    <!-- ====================================================================== -->
+    <!-- P R O J E C T D E S C R I P T I O N -->
+    <!-- ====================================================================== -->
+    <parent>
+        <groupId>org.apache.jackrabbit</groupId>
+        <artifactId>jackrabbit-parent</artifactId>
+        <version>2.10.1</version>
+        <relativePath>../jackrabbit-parent/pom.xml</relativePath>
+    </parent>
+    <artifactId>jackrabbit-aws-ext</artifactId>
+    <name>Jackrabbit AWS Extension</name>
+    <description>Jackrabbit extenstion to Amazon Webservices</description>
+    <packaging>bundle</packaging>
+
+    <!-- ====================================================================== -->
+    <!-- D E P E N D E N C I E S -->
+    <!-- ====================================================================== -->
+    <dependencies>
+        <dependency>
+            <groupId>javax.jcr</groupId>
+            <artifactId>jcr</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-jcr-commons</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.amazonaws</groupId>
+            <artifactId>aws-java-sdk-s3</artifactId>
+            <version>1.9.22</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-data</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>jackrabbit-data</artifactId>
+            <version>${project.version}</version>
+            <type>test-jar</type>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <version>1.7.5</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <includes>
+                        <include>**/aws/**/TestAll.java</include>
+                    </includes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>org.apache.jackrabbit.aws.ext.ds</Export-Package>
+                        <DynamicImport-Package>sun.io</DynamicImport-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>.checkstyle</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/S3Constants.java b/jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/S3Constants.java
new file mode 100644
index 0000000..9621ca7
--- /dev/null
+++ b/jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/S3Constants.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.aws.ext;
+
+/**
+ * Defined Amazon S3 constants.
+ */
+public final class S3Constants {
+
+    /**
+     * Amazon aws access key.
+     */
+    public static final String ACCESS_KEY = "accessKey";
+
+    /**
+     * Amazon aws secret key.
+     */
+    public static final String SECRET_KEY = "secretKey";
+    
+    /**
+     * Amazon S3 Http connection timeout.
+     */
+    public static final String S3_CONN_TIMEOUT = "connectionTimeout";
+    
+    /**
+     * Amazon S3  socket timeout.
+     */
+    public static final String S3_SOCK_TIMEOUT = "socketTimeout";
+    
+    /**
+     * Amazon S3  maximum connections to be used.
+     */
+    public static final String S3_MAX_CONNS = "maxConnections";
+    
+    /**
+     * Amazon S3  maximum retries.
+     */
+    public static final String S3_MAX_ERR_RETRY = "maxErrorRetry";
+
+    /**
+     * Amazon aws S3 bucket.
+     */
+    public static final String S3_BUCKET = "s3Bucket";
+
+    /**
+     * Amazon aws S3 region.
+     */
+    public static final String S3_REGION = "s3Region";
+    
+    /**
+     * Amazon aws S3 region.
+     */
+    public static final String S3_END_POINT = "s3EndPoint";
+    
+    /**
+     * Constant for S3 Connector Protocol
+     */
+    public static final String S3_CONN_PROTOCOL = "s3ConnProtocol";
+
+    /**
+     * Constant to rename keys
+     */
+    public static final String S3_RENAME_KEYS = "s3RenameKeys";
+
+    /**
+     * Constant to rename keys
+     */
+    public static final String S3_WRITE_THREADS = "writeThreads";
+    
+    /**
+     * Constant to enable encryption in S3.
+     */
+    public static final String S3_ENCRYPTION = "s3Encryption";
+
+    /**
+     * Constant for no encryption. it is default.
+     */
+    public static final String S3_ENCRYPTION_NONE = "NONE";
+
+    /**
+     *  Constant to set SSE_S3 encryption.
+     */
+    public static final String S3_ENCRYPTION_SSE_S3 = "SSE_S3";
+
+    /**
+     * private constructor so that class cannot initialized from outside.
+     */
+    private S3Constants() {
+
+    }
+
+}
diff --git a/jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/S3RequestDecorator.java b/jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/S3RequestDecorator.java
new file mode 100644
index 0000000..2fcc244
--- /dev/null
+++ b/jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/S3RequestDecorator.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.aws.ext;
+
+import java.util.Properties;
+
+import com.amazonaws.services.s3.model.CopyObjectRequest;
+import com.amazonaws.services.s3.model.ObjectMetadata;
+import com.amazonaws.services.s3.model.PutObjectRequest;
+
+/**
+ * This class to sets encrption mode in S3 request.
+ *
+ */
+public class S3RequestDecorator {
+    DataEncryption dataEncryption = DataEncryption.NONE;
+
+    public S3RequestDecorator(Properties props) {
+        if (props.getProperty(S3Constants.S3_ENCRYPTION) != null) {
+            this.dataEncryption = dataEncryption.valueOf(props.getProperty(S3Constants.S3_ENCRYPTION));
+        }
+    }
+    
+    /**
+     * Set encryption in {@link PutObjectRequest}
+     */
+    public PutObjectRequest decorate(PutObjectRequest request) {
+        switch (getDataEncryption()) {
+            case SSE_S3:
+                ObjectMetadata metadata = request.getMetadata() == null
+                                ? new ObjectMetadata()
+                                : request.getMetadata();
+                metadata.setSSEAlgorithm(ObjectMetadata.AES_256_SERVER_SIDE_ENCRYPTION);
+                request.setMetadata(metadata);
+                break;
+            case NONE:
+                break;
+        }
+        return request;
+    }
+
+    /**
+     * Set encryption in {@link CopyObjectRequest}
+     */
+    public CopyObjectRequest decorate(CopyObjectRequest request) {
+        switch (getDataEncryption()) {
+            case SSE_S3:
+                ObjectMetadata metadata = request.getNewObjectMetadata() == null
+                                ? new ObjectMetadata()
+                                : request.getNewObjectMetadata();
+                metadata.setSSEAlgorithm(ObjectMetadata.AES_256_SERVER_SIDE_ENCRYPTION);
+                request.setNewObjectMetadata(metadata);
+                break;
+            case NONE:
+                break;
+        }
+        return request;
+    }
+
+    private DataEncryption getDataEncryption() {
+        return this.dataEncryption;
+    }
+
+    /**
+     * Enum to indicate S3 encryption mode 
+     *
+     */
+    private enum DataEncryption {
+        SSE_S3, NONE;
+    }
+
+}
diff --git a/jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/Utils.java b/jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/Utils.java
new file mode 100644
index 0000000..d5d0ba1
--- /dev/null
+++ b/jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/Utils.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.aws.ext;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.amazonaws.ClientConfiguration;
+import com.amazonaws.Protocol;
+import com.amazonaws.auth.AWSCredentials;
+import com.amazonaws.auth.BasicAWSCredentials;
+import com.amazonaws.services.s3.model.Region;
+import com.amazonaws.services.s3.AmazonS3;
+import com.amazonaws.services.s3.AmazonS3Client;
+import com.amazonaws.services.s3.model.ObjectListing;
+import com.amazonaws.services.s3.model.S3ObjectSummary;
+
+/**
+ * Amazon S3 utilities.
+ */
+public final class Utils {
+
+    private static final Logger LOG = LoggerFactory.getLogger(Utils.class);
+
+    public static final String DEFAULT_CONFIG_FILE = "aws.properties";
+
+    private static final String DELETE_CONFIG_SUFFIX = ";burn";
+
+    /**
+     * The default value AWS bucket region.
+     */
+    public static final String DEFAULT_AWS_BUCKET_REGION = "us-standard";
+
+    /**
+     * constants to define endpoint to various AWS region
+     */
+    public static final String AWSDOTCOM = "amazonaws.com";
+
+    public static final String S3 = "s3";
+
+    public static final String DOT = ".";
+
+    public static final String DASH = "-";
+
+    /**
+     * private constructor so that class cannot initialized from outside.
+     */
+    private Utils() {
+
+    }
+
+    /**
+     * Create AmazonS3Client from properties.
+     * 
+     * @param prop properties to configure @link {@link AmazonS3Client}
+     * @return {@link AmazonS3Client}
+     */
+    public static AmazonS3Client openService(final Properties prop) {
+        AWSCredentials credentials = new BasicAWSCredentials(
+            prop.getProperty(S3Constants.ACCESS_KEY),
+            prop.getProperty(S3Constants.SECRET_KEY));
+        AmazonS3Client s3service =  new AmazonS3Client(credentials, getClientConfiguration(prop));
+        String region = prop.getProperty(S3Constants.S3_REGION);
+        String endpoint = null;
+        String propEndPoint = prop.getProperty(S3Constants.S3_END_POINT);
+        if ((propEndPoint != null) & !"".equals(propEndPoint)) {
+            endpoint = propEndPoint;
+        } else {
+            if (DEFAULT_AWS_BUCKET_REGION.equals(region)) {
+                endpoint = S3 + DOT + AWSDOTCOM;
+            } else if (Region.EU_Ireland.toString().equals(region)) {
+                endpoint = "s3-eu-west-1" + DOT + AWSDOTCOM;
+            } else {
+                endpoint = S3 + DASH + region + DOT + AWSDOTCOM;
+            }
+        }
+        /*
+         * setting endpoint to remove latency of redirection. If endpoint is
+         * not set, invocation first goes us standard region, which
+         * redirects it to correct location.
+         */
+        s3service.setEndpoint(endpoint);
+        LOG.info("S3 service endpoint [{}] ", endpoint);
+        return s3service;
+    }
+
+    /**
+     * Delete S3 bucket. This method first deletes all objects from bucket and
+     * then delete empty bucket.
+     * 
+     * @param bucketName the bucket name.
+     */
+    public static void deleteBucket(final String bucketName) throws IOException {
+        Properties prop = readConfig(DEFAULT_CONFIG_FILE);
+        AmazonS3 s3service = openService(prop);
+        ObjectListing prevObjectListing = s3service.listObjects(bucketName);
+        while (true) {
+            for (S3ObjectSummary s3ObjSumm : prevObjectListing.getObjectSummaries()) {
+                s3service.deleteObject(bucketName, s3ObjSumm.getKey());
+            }
+            if (!prevObjectListing.isTruncated()) {
+                break;
+            }
+            prevObjectListing = s3service.listNextBatchOfObjects(prevObjectListing);
+        }
+        s3service.deleteBucket(bucketName);
+    }
+
+    /**
+     * Read a configuration properties file. If the file name ends with ";burn",
+     * the file is deleted after reading.
+     * 
+     * @param fileName the properties file name
+     * @return the properties
+     * @throws IOException if the file doesn't exist
+     */
+    public static Properties readConfig(String fileName) throws IOException {
+        boolean delete = false;
+        if (fileName.endsWith(DELETE_CONFIG_SUFFIX)) {
+            delete = true;
+            fileName = fileName.substring(0, fileName.length()
+                - DELETE_CONFIG_SUFFIX.length());
+        }
+        if (!new File(fileName).exists()) {
+            throw new IOException("Config file not found: " + fileName);
+        }
+        Properties prop = new Properties();
+        InputStream in = null;
+        try {
+            in = new FileInputStream(fileName);
+            prop.load(in);
+        } finally {
+            if (in != null) {
+                in.close();
+            }
+            if (delete) {
+                deleteIfPossible(new File(fileName));
+            }
+        }
+        return prop;
+    }
+
+    private static void deleteIfPossible(final File file) {
+        boolean deleted = file.delete();
+        if (!deleted) {
+            LOG.warn("Could not delete " + file.getAbsolutePath());
+        }
+    }
+
+    private static ClientConfiguration getClientConfiguration(Properties prop) {
+        int connectionTimeOut = Integer.parseInt(prop.getProperty(S3Constants.S3_CONN_TIMEOUT));
+        int socketTimeOut = Integer.parseInt(prop.getProperty(S3Constants.S3_SOCK_TIMEOUT));
+        int maxConnections = Integer.parseInt(prop.getProperty(S3Constants.S3_MAX_CONNS));
+        int maxErrorRetry = Integer.parseInt(prop.getProperty(S3Constants.S3_MAX_ERR_RETRY));
+        ClientConfiguration cc = new ClientConfiguration();
+        String protocol = prop.getProperty(S3Constants.S3_CONN_PROTOCOL);
+        if (protocol != null && protocol.equalsIgnoreCase("http")) {
+            cc.setProtocol(Protocol.HTTP);
+        }
+        cc.setConnectionTimeout(connectionTimeOut);
+        cc.setSocketTimeout(socketTimeOut);
+        cc.setMaxConnections(maxConnections);
+        cc.setMaxErrorRetry(maxErrorRetry);
+        return cc;
+    }
+
+}
diff --git a/jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/ds/S3Backend.java b/jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/ds/S3Backend.java
new file mode 100644
index 0000000..7436034
--- /dev/null
+++ b/jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/ds/S3Backend.java
@@ -0,0 +1,912 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.aws.ext.ds;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.jackrabbit.aws.ext.S3Constants;
+import org.apache.jackrabbit.aws.ext.S3RequestDecorator;
+import org.apache.jackrabbit.aws.ext.Utils;
+import org.apache.jackrabbit.core.data.AsyncTouchCallback;
+import org.apache.jackrabbit.core.data.AsyncTouchResult;
+import org.apache.jackrabbit.core.data.AsyncUploadCallback;
+import org.apache.jackrabbit.core.data.AsyncUploadResult;
+import org.apache.jackrabbit.core.data.Backend;
+import org.apache.jackrabbit.core.data.CachingDataStore;
+import org.apache.jackrabbit.core.data.DataIdentifier;
+import org.apache.jackrabbit.core.data.DataStoreException;
+import org.apache.jackrabbit.core.data.util.NamedThreadFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.amazonaws.AmazonClientException;
+import com.amazonaws.AmazonServiceException;
+import com.amazonaws.event.ProgressEvent;
+import com.amazonaws.event.ProgressListener;
+import com.amazonaws.services.s3.AmazonS3Client;
+import com.amazonaws.services.s3.model.CopyObjectRequest;
+import com.amazonaws.services.s3.model.DeleteObjectsRequest;
+import com.amazonaws.services.s3.model.DeleteObjectsResult;
+import com.amazonaws.services.s3.model.ObjectListing;
+import com.amazonaws.services.s3.model.ObjectMetadata;
+import com.amazonaws.services.s3.model.PutObjectRequest;
+import com.amazonaws.services.s3.model.Region;
+import com.amazonaws.services.s3.model.S3Object;
+import com.amazonaws.services.s3.model.S3ObjectSummary;
+import com.amazonaws.services.s3.transfer.Copy;
+import com.amazonaws.services.s3.transfer.TransferManager;
+import com.amazonaws.services.s3.transfer.Upload;
+
+/**
+ * A data store backend that stores data on Amazon S3.
+ */
+public class S3Backend implements Backend {
+
+    /**
+     * Logger instance.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(S3Backend.class);
+
+    private static final String KEY_PREFIX = "dataStore_";
+
+    private AmazonS3Client s3service;
+
+    private String bucket;
+
+    private TransferManager tmx;
+
+    private CachingDataStore store;
+
+    private Properties properties;
+
+    private Date startTime;
+    
+    private ThreadPoolExecutor asyncWriteExecuter;
+
+    private S3RequestDecorator s3ReqDecorator;
+
+    /**
+     * Initialize S3Backend. It creates AmazonS3Client and TransferManager from
+     * aws.properties. It creates S3 bucket if it doesn't pre-exist in S3.
+     */
+    @Override
+    public void init(CachingDataStore store, String homeDir, String config)
+            throws DataStoreException {
+        Properties initProps = null;
+        //Check is configuration is already provided. That takes precedence
+        //over config provided via file based config
+        if(this.properties != null){
+            initProps = this.properties;
+        } else {
+            if(config == null){
+                config = Utils.DEFAULT_CONFIG_FILE;
+            }
+            try{
+                initProps = Utils.readConfig(config);
+            }catch(IOException e){
+                throw new DataStoreException("Could not initialize S3 from "
+                        + config, e);
+            }
+            this.properties = initProps;
+        }
+        init(store, homeDir, initProps);
+    }
+
+    public void init(CachingDataStore store, String homeDir, Properties prop)
+            throws DataStoreException {
+
+        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+        try {
+            startTime = new Date();
+            Thread.currentThread().setContextClassLoader(
+                getClass().getClassLoader());
+            LOG.debug("init");
+            this.store = store;
+            s3ReqDecorator = new S3RequestDecorator(prop);
+
+            s3service = Utils.openService(prop);
+            if (bucket == null || "".equals(bucket.trim())) {
+                bucket = prop.getProperty(S3Constants.S3_BUCKET);
+            }
+            String region = prop.getProperty(S3Constants.S3_REGION);
+            Region s3Region = null;
+            if (Utils.DEFAULT_AWS_BUCKET_REGION.equals(region)) {
+                s3Region =  Region.US_Standard;
+            } else if (Region.EU_Ireland.toString().equals(region)) {
+                s3Region = Region.EU_Ireland;
+            } else {
+                s3Region = Region.fromValue(region);
+            }
+            
+            if (!s3service.doesBucketExist(bucket)) {
+                s3service.createBucket(bucket, s3Region);
+                LOG.info("Created bucket [{}] in [{}] ", bucket, region);
+            } else {
+                LOG.info("Using bucket [{}] in [{}] ", bucket, region);
+            }
+           
+            int writeThreads = 10;
+            String writeThreadsStr = prop.getProperty(S3Constants.S3_WRITE_THREADS);
+            if (writeThreadsStr != null) {
+                writeThreads = Integer.parseInt(writeThreadsStr);
+            }
+            LOG.info("Using thread pool of [{}] threads in S3 transfer manager.", writeThreads);
+            tmx = new TransferManager(s3service,
+                (ThreadPoolExecutor) Executors.newFixedThreadPool(writeThreads,
+                    new NamedThreadFactory("s3-transfer-manager-worker")));
+            
+            int asyncWritePoolSize = 10;
+            String maxConnsStr = prop.getProperty(S3Constants.S3_MAX_CONNS);
+            if (maxConnsStr != null) {
+                asyncWritePoolSize = Integer.parseInt(maxConnsStr)
+                    - writeThreads;
+            }
+            
+            asyncWriteExecuter = (ThreadPoolExecutor) Executors.newFixedThreadPool(
+                asyncWritePoolSize, new NamedThreadFactory("s3-write-worker"));
+            String renameKeyProp = prop.getProperty(S3Constants.S3_RENAME_KEYS);
+            boolean renameKeyBool = (renameKeyProp == null || "".equals(renameKeyProp))
+                    ? false
+                    : Boolean.parseBoolean(renameKeyProp);
+            LOG.info("Rename keys [{}]", renameKeyBool);
+            if (renameKeyBool) {
+                renameKeys();
+            }
+            LOG.debug("S3 Backend initialized in [{}] ms",
+                +(System.currentTimeMillis() - startTime.getTime()));
+        } catch (Exception e) {
+            LOG.debug("  error ", e);
+            throw new DataStoreException("Could not initialize S3 from "
+                + prop, e);
+        } finally {
+            if (contextClassLoader != null) {
+                Thread.currentThread().setContextClassLoader(contextClassLoader);
+            }
+        }
+    }
+
+    /**
+     * It uploads file to Amazon S3. If file size is greater than 5MB, this
+     * method uses parallel concurrent connections to upload.
+     */
+    @Override
+    public void write(DataIdentifier identifier, File file)
+            throws DataStoreException {
+        this.write(identifier, file, false, null);
+
+    }
+
+    @Override
+    public void writeAsync(DataIdentifier identifier, File file,
+            AsyncUploadCallback callback) throws DataStoreException {
+        if (callback == null) {
+            throw new IllegalArgumentException(
+                "callback parameter cannot be null in asyncUpload");
+        }
+        asyncWriteExecuter.execute(new AsyncUploadJob(identifier, file,
+            callback));
+    }
+
+    /**
+     * Check if record identified by identifier exists in Amazon S3.
+     */
+    @Override
+    public boolean exists(DataIdentifier identifier) throws DataStoreException {
+        long start = System.currentTimeMillis();
+        String key = getKeyName(identifier);
+        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(
+                getClass().getClassLoader());
+            ObjectMetadata objectMetaData = s3service.getObjectMetadata(bucket,
+                key);
+            if (objectMetaData != null) {
+                LOG.debug("exists [{}]: [true] took [{}] ms.",
+                    identifier, (System.currentTimeMillis() - start) );
+                return true;
+            }
+            return false;
+        } catch (AmazonServiceException e) {
+            if (e.getStatusCode() == 404 || e.getStatusCode() == 403) {
+                LOG.debug("exists [{}]: [false] took [{}] ms.",
+                    identifier, (System.currentTimeMillis() - start) );
+                return false;
+            }
+            throw new DataStoreException(
+                "Error occured to getObjectMetadata for key ["
+                    + identifier.toString() + "]", e);
+        } finally {
+            if (contextClassLoader != null) {
+                Thread.currentThread().setContextClassLoader(contextClassLoader);
+            }
+        }
+    }
+
+    @Override
+    public boolean exists(DataIdentifier identifier, boolean touch)
+            throws DataStoreException {
+        long start = System.currentTimeMillis();
+        String key = getKeyName(identifier);
+        ObjectMetadata objectMetaData = null;
+        boolean retVal = false;
+        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(
+                getClass().getClassLoader());
+            objectMetaData = s3service.getObjectMetadata(bucket, key);
+            if (objectMetaData != null) {
+                retVal = true;
+                if (touch) {
+                    CopyObjectRequest copReq = new CopyObjectRequest(bucket,
+                        key, bucket, key);
+                    copReq.setNewObjectMetadata(objectMetaData);
+                    Copy copy = tmx.copy(s3ReqDecorator.decorate(copReq));
+                    copy.waitForCopyResult();
+                    LOG.debug("[{}] touched took [{}] ms. ", identifier,
+                        (System.currentTimeMillis() - start));
+                }
+            } else {
+                retVal = false;
+            }
+
+        } catch (AmazonServiceException e) {
+            if (e.getStatusCode() == 404 || e.getStatusCode() == 403) {
+                retVal = false;
+            } else {
+                throw new DataStoreException(
+                    "Error occured to find exists for key ["
+                        + identifier.toString() + "]", e);
+            }
+        } catch (Exception e) {
+            throw new DataStoreException(
+                "Error occured to find exists for key  "
+                    + identifier.toString(), e);
+        } finally {
+            if (contextClassLoader != null) {
+                Thread.currentThread().setContextClassLoader(contextClassLoader);
+            }
+        }
+        LOG.debug("exists [{}]: [{}] took [{}] ms.", new Object[] { identifier,
+            retVal, (System.currentTimeMillis() - start) });
+        return retVal;
+    }
+    
+    @Override
+    public void touchAsync(final DataIdentifier identifier,
+            final long minModifiedDate, final AsyncTouchCallback callback)
+            throws DataStoreException {
+        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+        try {
+            if (callback == null) {
+                throw new IllegalArgumentException(
+                    "callback parameter cannot be null in touchAsync");
+            }
+            Thread.currentThread().setContextClassLoader(
+                getClass().getClassLoader());
+
+            asyncWriteExecuter.execute(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        touch(identifier, minModifiedDate);
+                        callback.onSuccess(new AsyncTouchResult(identifier));
+                    } catch (DataStoreException e) {
+                        AsyncTouchResult result = new AsyncTouchResult(
+                            identifier);
+                        result.setException(e);
+                        callback.onFailure(result);
+                    }
+                }
+            });
+        } catch (Exception e) {
+            callback.onAbort(new AsyncTouchResult(identifier));
+            throw new DataStoreException("Cannot touch the record "
+                + identifier.toString(), e);
+        } finally {
+            if (contextClassLoader != null) {
+                Thread.currentThread().setContextClassLoader(contextClassLoader);
+            }
+        }
+
+    }
+
+    @Override
+    public void touch(DataIdentifier identifier, long minModifiedDate)
+            throws DataStoreException {
+        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+        try {
+            final long start = System.currentTimeMillis();
+            final String key = getKeyName(identifier);
+            if (minModifiedDate > 0
+                && minModifiedDate > getLastModified(identifier)) {
+                CopyObjectRequest copReq = new CopyObjectRequest(bucket, key,
+                    bucket, key);
+                copReq.setNewObjectMetadata(new ObjectMetadata());
+                Copy copy = tmx.copy(s3ReqDecorator.decorate(copReq));
+                copy.waitForCompletion();
+                LOG.debug("[{}] touched. time taken [{}] ms ", new Object[] {
+                    identifier, (System.currentTimeMillis() - start) });
+            } else {
+                LOG.debug("[{}] touch not required. time taken [{}] ms ",
+                    new Object[] { identifier,
+                        (System.currentTimeMillis() - start) });
+            }
+
+        } catch (Exception e) {
+            throw new DataStoreException("Error occured in touching key ["
+                + identifier.toString() + "]", e);
+        } finally {
+            if (contextClassLoader != null) {
+                Thread.currentThread().setContextClassLoader(contextClassLoader);
+            }
+        }
+    }
+
+    @Override
+    public InputStream read(DataIdentifier identifier)
+            throws DataStoreException {
+        long start = System.currentTimeMillis();
+        String key = getKeyName(identifier);
+        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(
+                getClass().getClassLoader());
+            S3Object object = s3service.getObject(bucket, key);
+            InputStream in = object.getObjectContent();
+            LOG.debug("[{}] read took [{}]ms", identifier,
+                (System.currentTimeMillis() - start));
+            return in;
+        } catch (AmazonServiceException e) {
+            throw new DataStoreException("Object not found: " + key, e);
+        } finally {
+            if (contextClassLoader != null) {
+                Thread.currentThread().setContextClassLoader(contextClassLoader);
+            }
+        }
+    }
+
+    @Override
+    public Iterator<DataIdentifier> getAllIdentifiers()
+            throws DataStoreException {
+        long start = System.currentTimeMillis();
+        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(
+                getClass().getClassLoader());
+            Set<DataIdentifier> ids = new HashSet<DataIdentifier>();
+            ObjectListing prevObjectListing = s3service.listObjects(bucket);
+            while (true) {
+                for (S3ObjectSummary s3ObjSumm : prevObjectListing.getObjectSummaries()) {
+                    String id = getIdentifierName(s3ObjSumm.getKey());
+                    if (id != null) {
+                        ids.add(new DataIdentifier(id));
+                    }
+                }
+                if (!prevObjectListing.isTruncated()) break;
+                prevObjectListing = s3service.listNextBatchOfObjects(prevObjectListing);
+            }
+            LOG.debug("getAllIdentifiers returned size [{}] took [{}] ms.",
+                ids.size(), (System.currentTimeMillis() - start));
+            return ids.iterator();
+        } catch (AmazonServiceException e) {
+            throw new DataStoreException("Could not list objects", e);
+        } finally {
+            if (contextClassLoader != null) {
+                Thread.currentThread().setContextClassLoader(contextClassLoader);
+            }
+        }
+    }
+
+    @Override
+    public long getLastModified(DataIdentifier identifier)
+            throws DataStoreException {
+        long start = System.currentTimeMillis();
+        String key = getKeyName(identifier);
+        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(
+                getClass().getClassLoader());
+            ObjectMetadata object = s3service.getObjectMetadata(bucket, key);
+            long lastModified = object.getLastModified().getTime();
+            LOG.debug(
+                "Identifier [{}]'s lastModified = [{}] took [{}]ms.",
+                new Object[] { identifier, lastModified,
+                    (System.currentTimeMillis() - start) });
+            return lastModified;
+        } catch (AmazonServiceException e) {
+            if (e.getStatusCode() == 404 || e.getStatusCode() == 403) {
+                LOG.info(
+                    "getLastModified:Identifier [{}] not found. Took [{}] ms.",
+                    identifier, (System.currentTimeMillis() - start));
+            }
+            throw new DataStoreException(e);
+        } finally {
+            if (contextClassLoader != null) {
+                Thread.currentThread().setContextClassLoader(contextClassLoader);
+            }
+        }
+    }
+
+    @Override
+    public long getLength(DataIdentifier identifier) throws DataStoreException {
+        long start = System.currentTimeMillis();
+        String key = getKeyName(identifier);
+        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(
+                getClass().getClassLoader());
+            ObjectMetadata object = s3service.getObjectMetadata(bucket, key);
+            long length = object.getContentLength();
+            LOG.debug("Identifier [{}]'s length = [{}] took [{}]ms.",
+                new Object[] { identifier, length,
+                    (System.currentTimeMillis() - start) });
+            return length;
+        } catch (AmazonServiceException e) {
+            throw new DataStoreException("Could not length of dataIdentifier "
+                + identifier, e);
+        } finally {
+            if (contextClassLoader != null) {
+                Thread.currentThread().setContextClassLoader(contextClassLoader);
+            }
+        }
+    }
+
+    @Override
+    public void deleteRecord(DataIdentifier identifier)
+            throws DataStoreException {
+        long start = System.currentTimeMillis();
+        String key = getKeyName(identifier);
+        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(
+                getClass().getClassLoader());
+            s3service.deleteObject(bucket, key);
+            LOG.debug("Identifier [{}] deleted. It took [{}]ms.", new Object[] {
+                identifier, (System.currentTimeMillis() - start) });
+        } catch (AmazonServiceException e) {
+            throw new DataStoreException(
+                "Could not getLastModified of dataIdentifier " + identifier, e);
+        } finally {
+            if (contextClassLoader != null) {
+                Thread.currentThread().setContextClassLoader(contextClassLoader);
+            }
+        }
+    }
+
+    @Override
+    public Set<DataIdentifier> deleteAllOlderThan(long min)
+            throws DataStoreException {
+        long start = System.currentTimeMillis();
+        // S3 stores lastModified to lower boundary of timestamp in ms.
+        // and hence min is reduced by 1000ms.
+        min = min - 1000;
+        Set<DataIdentifier> deleteIdSet = new HashSet<DataIdentifier>(30);
+        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(
+                getClass().getClassLoader());
+            ObjectListing prevObjectListing = s3service.listObjects(bucket);
+            while (true) {
+                List<DeleteObjectsRequest.KeyVersion> deleteList = new ArrayList<DeleteObjectsRequest.KeyVersion>();
+                for (S3ObjectSummary s3ObjSumm : prevObjectListing.getObjectSummaries()) {
+                    DataIdentifier identifier = new DataIdentifier(
+                        getIdentifierName(s3ObjSumm.getKey()));
+                    long lastModified = s3ObjSumm.getLastModified().getTime();
+                    LOG.debug("Identifier [{}]'s lastModified = [{}]", identifier, lastModified);
+                    if (lastModified < min
+                        && store.confirmDelete(identifier)
+                         // confirm once more that record's lastModified < min
+                        //  order is important here
+                        && s3service.getObjectMetadata(bucket,
+                            s3ObjSumm.getKey()).getLastModified().getTime() < min) {
+                       
+                        store.deleteFromCache(identifier);
+                        LOG.debug("add id [{}] to delete lists",
+                            s3ObjSumm.getKey());
+                        deleteList.add(new DeleteObjectsRequest.KeyVersion(
+                            s3ObjSumm.getKey()));
+                        deleteIdSet.add(identifier);
+                    }
+                }
+                if (deleteList.size() > 0) {
+                    DeleteObjectsRequest delObjsReq = new DeleteObjectsRequest(
+                        bucket);
+                    delObjsReq.setKeys(deleteList);
+                    DeleteObjectsResult dobjs = s3service.deleteObjects(delObjsReq);
+                    if (dobjs.getDeletedObjects().size() != deleteList.size()) {
+                        throw new DataStoreException(
+                            "Incomplete delete object request. only  "
+                                + dobjs.getDeletedObjects().size() + " out of "
+                                + deleteList.size() + " are deleted");
+                    } else {
+                        LOG.debug("[{}] records deleted from datastore",
+                            deleteList);
+                    }
+                }
+                if (!prevObjectListing.isTruncated()) {
+                    break;
+                }
+                prevObjectListing = s3service.listNextBatchOfObjects(prevObjectListing);
+            }
+        } finally {
+            if (contextClassLoader != null) {
+                Thread.currentThread().setContextClassLoader(contextClassLoader);
+            }
+        }
+        LOG.info(
+            "deleteAllOlderThan: min=[{}] exit. Deleted[{}] records. Number of records deleted [{}] took [{}]ms",
+            new Object[] { min, deleteIdSet, deleteIdSet.size(),
+                (System.currentTimeMillis() - start) });
+        return deleteIdSet;
+    }
+
+    @Override
+    public void close() {
+        // backend is closing. abort all mulitpart uploads from start.
+        asyncWriteExecuter.shutdownNow();
+        if(s3service.doesBucketExist(bucket)) {
+            tmx.abortMultipartUploads(bucket, startTime);
+        }
+        tmx.shutdownNow();
+        s3service.shutdown();
+        LOG.info("S3Backend closed.");
+    }
+
+    public String getBucket() {
+        return bucket;
+    }
+
+    public void setBucket(String bucket) {
+        this.bucket = bucket;
+    }
+
+    /**
+     * Properties used to configure the backend. If provided explicitly
+     * before init is invoked then these take precedence
+     *
+     * @param properties  to configure S3Backend
+     */
+    public void setProperties(Properties properties) {
+        this.properties = properties;
+    }
+
+    private void write(DataIdentifier identifier, File file,
+            boolean asyncUpload, AsyncUploadCallback callback)
+            throws DataStoreException {
+        String key = getKeyName(identifier);
+        ObjectMetadata objectMetaData = null;
+        long start = System.currentTimeMillis();
+        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+        try {
+            Thread.currentThread().setContextClassLoader(
+                getClass().getClassLoader());
+            // check if the same record already exists
+            try {
+                objectMetaData = s3service.getObjectMetadata(bucket, key);
+            } catch (AmazonServiceException ase) {
+                if (!(ase.getStatusCode() == 404 || ase.getStatusCode() == 403)) {
+                    throw ase;
+                }
+            }
+            if (objectMetaData != null) {
+                long l = objectMetaData.getContentLength();
+                if (l != file.length()) {
+                    throw new DataStoreException("Collision: " + key
+                        + " new length: " + file.length() + " old length: " + l);
+                }
+                LOG.debug("[{}]'s exists, lastmodified = [{}]", key,
+                    objectMetaData.getLastModified().getTime());
+                CopyObjectRequest copReq = new CopyObjectRequest(bucket, key,
+                    bucket, key);
+                copReq.setNewObjectMetadata(objectMetaData);
+                Copy copy = tmx.copy(s3ReqDecorator.decorate(copReq));
+                try {
+                    copy.waitForCopyResult();
+                    LOG.debug("lastModified of [{}] updated successfully.", identifier);
+                    if (callback != null) {
+                        callback.onSuccess(new AsyncUploadResult(identifier, file));
+                    }
+                }catch (Exception e2) {
+                    AsyncUploadResult asyncUpRes= new AsyncUploadResult(identifier, file);
+                    asyncUpRes.setException(e2);
+                    if (callback != null) {
+                        callback.onAbort(asyncUpRes);
+                    }
+                    throw new DataStoreException("Could not upload " + key, e2);
+                }
+            }
+
+            if (objectMetaData == null) {
+                try {
+                    // start multipart parallel upload using amazon sdk
+                    Upload up = tmx.upload(s3ReqDecorator.decorate(new PutObjectRequest(
+                        bucket, key, file)));
+                    // wait for upload to finish
+                    if (asyncUpload) {
+                        up.addProgressListener(new S3UploadProgressListener(up,
+                            identifier, file, callback));
+                        LOG.debug(
+                            "added upload progress listener to identifier [{}]",
+                            identifier);
+                    } else {
+                        up.waitForUploadResult();
+                        LOG.debug("synchronous upload to identifier [{}] completed.", identifier); 
+                        if (callback != null) {
+                            callback.onSuccess(new AsyncUploadResult(
+                                identifier, file));
+                        }
+                    }
+                } catch (Exception e2 ) {
+                    AsyncUploadResult asyncUpRes= new AsyncUploadResult(identifier, file);
+                    asyncUpRes.setException(e2);
+                    if (callback != null) {
+                        callback.onAbort(asyncUpRes);
+                    } 
+                    throw new DataStoreException("Could not upload " + key, e2);
+                }
+            }
+        } finally {
+            if (contextClassLoader != null) {
+                Thread.currentThread().setContextClassLoader(contextClassLoader);
+            }
+        }
+        LOG.debug(
+            "write of [{}], length=[{}], in async mode [{}], in [{}]ms",
+            new Object[] { identifier, file.length(), asyncUpload,
+                (System.currentTimeMillis() - start) });
+    }
+
+    /**
+     * This method rename object keys in S3 concurrently. The number of
+     * concurrent threads is defined by 'maxConnections' property in
+     * aws.properties. As S3 doesn't have "move" command, this method simulate
+     * move as copy object object to new key and then delete older key.
+     */
+    private void renameKeys() throws DataStoreException {
+        long startTime = System.currentTimeMillis();
+        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+        long count = 0;
+        try {
+            Thread.currentThread().setContextClassLoader(
+                getClass().getClassLoader());
+            ObjectListing prevObjectListing = s3service.listObjects(bucket);
+            List<DeleteObjectsRequest.KeyVersion> deleteList = new ArrayList<DeleteObjectsRequest.KeyVersion>();
+            int nThreads = Integer.parseInt(properties.getProperty("maxConnections"));
+            ExecutorService executor = Executors.newFixedThreadPool(nThreads,
+                new NamedThreadFactory("s3-object-rename-worker"));
+            boolean taskAdded = false;
+            while (true) {
+                for (S3ObjectSummary s3ObjSumm : prevObjectListing.getObjectSummaries()) {
+                    executor.execute(new KeyRenameThread(s3ObjSumm.getKey()));
+                    taskAdded = true;
+                    count++;
+                    // delete the object if it follows old key name format
+                    if( s3ObjSumm.getKey().startsWith(KEY_PREFIX)) {
+                        deleteList.add(new DeleteObjectsRequest.KeyVersion(
+                            s3ObjSumm.getKey()));
+                    }
+                }
+                if (!prevObjectListing.isTruncated()) break;
+                prevObjectListing = s3service.listNextBatchOfObjects(prevObjectListing);
+            }
+            // This will make the executor accept no new threads
+            // and finish all existing threads in the queue
+            executor.shutdown();
+
+            try {
+                // Wait until all threads are finish
+                while (taskAdded
+                    && !executor.awaitTermination(10, TimeUnit.SECONDS)) {
+                    LOG.info("Rename S3 keys tasks timedout. Waiting again");
+                }
+            } catch (InterruptedException ie) {
+
+            }
+            LOG.info("Renamed [{}] keys, time taken [{}]sec", count,
+                ((System.currentTimeMillis() - startTime) / 1000));
+            // Delete older keys.
+            if (deleteList.size() > 0) {
+                DeleteObjectsRequest delObjsReq = new DeleteObjectsRequest(
+                    bucket);
+                int batchSize = 500, startIndex = 0, size = deleteList.size();
+                int endIndex = batchSize < size ? batchSize : size;
+                while (endIndex <= size) {
+                    delObjsReq.setKeys(Collections.unmodifiableList(deleteList.subList(
+                        startIndex, endIndex)));
+                    DeleteObjectsResult dobjs = s3service.deleteObjects(delObjsReq);
+                    LOG.info(
+                        "Records[{}] deleted in datastore from index [{}] to [{}]",
+                        new Object[] { dobjs.getDeletedObjects().size(),
+                            startIndex, (endIndex - 1) });
+                    if (endIndex == size) {
+                        break;
+                    } else {
+                        startIndex = endIndex;
+                        endIndex = (startIndex + batchSize) < size
+                                ? (startIndex + batchSize)
+                                : size;
+                    }
+                }
+            }
+        } finally {
+            if (contextClassLoader != null) {
+                Thread.currentThread().setContextClassLoader(contextClassLoader);
+            }
+        }
+    }
+
+    /**
+     * The method convert old key format to new format. For e.g. this method
+     * converts old key dataStore_004cb70c8f87d78f04da41e7547cb434094089ea to
+     * 004c-b70c8f87d78f04da41e7547cb434094089ea.
+     */
+    private static String convertKey(String oldKey)
+            throws IllegalArgumentException {
+        if (!oldKey.startsWith(KEY_PREFIX)) {
+            return oldKey;
+        }
+        String key = oldKey.substring(KEY_PREFIX.length());
+        return key.substring(0, 4) + Utils.DASH + key.substring(4);
+    }
+
+    /**
+     * Get key from data identifier. Object is stored with key in S3.
+     */
+    private static String getKeyName(DataIdentifier identifier) {
+        String key = identifier.toString();
+        return key.substring(0, 4) + Utils.DASH + key.substring(4);
+    }
+
+    /**
+     * Get data identifier from key.
+     */
+    private static String getIdentifierName(String key) {
+        if (!key.contains(Utils.DASH)) {
+            return null;
+        }
+        return key.substring(0, 4) + key.substring(5);
+    }
+    
+
+    /**
+     * The class renames object key in S3 in a thread.
+     */
+    private class KeyRenameThread implements Runnable {
+
+        private String oldKey;
+
+        public void run() {
+            ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+            try {
+                Thread.currentThread().setContextClassLoader(
+                    getClass().getClassLoader());
+                String newS3Key = convertKey(oldKey);
+                CopyObjectRequest copReq = new CopyObjectRequest(bucket,
+                    oldKey, bucket, newS3Key);
+                Copy copy = tmx.copy(s3ReqDecorator.decorate(copReq));
+                try {
+                    copy.waitForCopyResult();
+                    LOG.debug("[{}] renamed to [{}] ", oldKey, newS3Key);
+                } catch (InterruptedException ie) {
+                    LOG.error(" Exception in renaming [{}] to [{}] ",
+                        new Object[] { ie, oldKey, newS3Key });
+                }
+            } finally {
+                if (contextClassLoader != null) {
+                    Thread.currentThread().setContextClassLoader(
+                        contextClassLoader);
+                }
+            }
+        }
+
+        public KeyRenameThread(String oldKey) {
+            this.oldKey = oldKey;
+        }
+    }
+
+    /**
+     * Listener which receives callback on status of S3 upload.
+     */
+    private class S3UploadProgressListener implements ProgressListener {
+
+        private File file;
+
+        private DataIdentifier identifier;
+
+        private AsyncUploadCallback callback;
+        
+        private Upload upload;
+
+        public S3UploadProgressListener(Upload upload, DataIdentifier identifier, File file,
+                AsyncUploadCallback callback) {
+            super();
+            this.identifier = identifier;
+            this.file = file;
+            this.callback = callback;
+            this.upload = upload;
+        }
+
+        public void progressChanged(ProgressEvent progressEvent) {
+            switch (progressEvent.getEventCode()) {
+                case ProgressEvent.COMPLETED_EVENT_CODE:
+                    callback.onSuccess(new AsyncUploadResult(identifier, file));
+                    break;
+                case ProgressEvent.FAILED_EVENT_CODE:
+                    AsyncUploadResult result = new AsyncUploadResult(
+                        identifier, file);
+                    try {
+                        AmazonClientException e = upload.waitForException();
+                        if (e != null) {
+                            result.setException(e);
+                        }
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                    }
+                    callback.onFailure(result);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+    
+    /**
+     * This class implements {@link Runnable} interface to upload {@link File}
+     * to S3 asynchronously.
+     */
+    private class AsyncUploadJob implements Runnable {
+
+        private DataIdentifier identifier;
+
+        private File file;
+
+        private AsyncUploadCallback callback;
+
+        public AsyncUploadJob(DataIdentifier identifier, File file,
+                AsyncUploadCallback callback) {
+            super();
+            this.identifier = identifier;
+            this.file = file;
+            this.callback = callback;
+        }
+
+        public void run() {
+            try {
+                write(identifier, file, true, callback);
+            } catch (DataStoreException e) {
+                LOG.error("Could not upload [" + identifier + "], file[" + file
+                    + "]", e);
+            }
+
+        }
+    }
+}
diff --git a/jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/ds/S3DataStore.java b/jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/ds/S3DataStore.java
new file mode 100644
index 0000000..8253572
--- /dev/null
+++ b/jackrabbit-aws-ext/src/main/java/org/apache/jackrabbit/aws/ext/ds/S3DataStore.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.aws.ext.ds;
+
+import java.util.Properties;
+
+import org.apache.jackrabbit.core.data.Backend;
+import org.apache.jackrabbit.core.data.CachingDataStore;
+
+/**
+ * An Amazon S3 data store.
+ */
+public class S3DataStore extends CachingDataStore {
+    private Properties properties;
+
+    @Override
+    protected Backend createBackend() {
+        S3Backend backend = new S3Backend();
+        if(properties != null){
+            backend.setProperties(properties);
+        }
+        return backend;
+    }
+
+    @Override
+    protected String getMarkerFile() {
+        return "s3.init.done";
+    }
+
+    /**
+     * Properties required to configure the S3Backend
+     */
+    public void setProperties(Properties properties) {
+        this.properties = properties;
+    }
+}
diff --git a/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/TestAll.java b/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/TestAll.java
new file mode 100644
index 0000000..fb289ad
--- /dev/null
+++ b/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/TestAll.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.aws.ext;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import org.apache.jackrabbit.aws.ext.ds.TestS3Ds;
+import org.apache.jackrabbit.aws.ext.ds.TestS3DSAsyncTouch;
+import org.apache.jackrabbit.aws.ext.ds.TestS3DsCacheOff;
+import org.apache.jackrabbit.aws.ext.ds.TestS3DSWithSSES3;
+import org.apache.jackrabbit.aws.ext.ds.TestS3DSWithSmallCache;
+import org.apache.jackrabbit.core.data.TestCaseBase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test suite that includes all test cases for the this module.
+ */
+public class TestAll extends TestCase {
+
+    private static final Logger LOG = LoggerFactory.getLogger(TestAll.class);
+
+    /**
+     * <code>TestAll</code> suite that executes all tests inside this module. To
+     * run test cases against Amazon S3 pass AWS configuration properties file as
+     * system property -Dconfig=/opt/cq/aws.properties. Sample aws properties
+     * located at src/test/resources/aws.properties.
+     */
+    public static Test suite() {
+        TestSuite suite = new TestSuite("S3 tests");
+        String config = System.getProperty(TestCaseBase.CONFIG);
+        LOG.info("config= " + config);
+        if (config != null && !"".equals(config.trim())) {
+            suite.addTestSuite(TestS3Ds.class);
+            suite.addTestSuite(TestS3DSAsyncTouch.class);
+            suite.addTestSuite(TestS3DSWithSmallCache.class);
+            suite.addTestSuite(TestS3DsCacheOff.class);
+            suite.addTestSuite(TestS3DSWithSSES3.class);
+        }
+        return suite;
+    }
+}
diff --git a/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/S3TestDataStore.java b/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/S3TestDataStore.java
new file mode 100644
index 0000000..33dbba4
--- /dev/null
+++ b/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/S3TestDataStore.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.aws.ext.ds;
+
+import java.util.Properties;
+
+import org.apache.jackrabbit.core.data.Backend;
+
+/**
+ * This class intialize {@link S3DataStore} with the give bucket. The other
+ * configuration are taken from configuration file. This class is implemented so
+ * that each test case run in its own bucket. It was required as deletions in
+ * bucket are not immediately reflected in the next test case.
+ */
+public class S3TestDataStore extends S3DataStore {
+
+    Properties props;
+
+    public S3TestDataStore() {
+        super();
+    }
+
+    public S3TestDataStore(Properties props) {
+        super();
+        this.props = props;
+    }
+
+    protected Backend createBackend() {
+        Backend backend = new S3Backend();
+        ((S3Backend) backend).setProperties(props);
+        return backend;
+    }
+}
diff --git a/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3DSAsyncTouch.java b/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3DSAsyncTouch.java
new file mode 100644
index 0000000..910d422
--- /dev/null
+++ b/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3DSAsyncTouch.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.aws.ext.ds;
+
+import java.io.IOException;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.core.data.CachingDataStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test {@link CachingDataStore} with
+ * {@link CachingDataStore#setTouchAsync(boolean) set to true. It requires to
+ * pass aws config file via system property. For e.g.
+ * -Dconfig=/opt/cq/aws.properties. Sample aws properties located at
+ * src/test/resources/aws.properties
+ */
+public class TestS3DSAsyncTouch extends TestS3Ds {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(TestS3DSAsyncTouch.class);
+
+    public TestS3DSAsyncTouch() throws IOException {
+
+    }
+
+    @Override
+    protected CachingDataStore createDataStore() throws RepositoryException {
+        S3DataStore s3ds = new S3DataStore();
+        s3ds.setProperties(props);
+        s3ds.setTouchAsync(true);
+        s3ds.setSecret("123456");
+        s3ds.init(dataStoreDir);
+        s3ds.updateModifiedDateOnAccess(System.currentTimeMillis() + 50 * 1000);
+        sleep(1000);
+        return s3ds;
+    }
+}
diff --git a/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3DSWithSSES3.java b/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3DSWithSSES3.java
new file mode 100644
index 0000000..45a2948
--- /dev/null
+++ b/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3DSWithSSES3.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.aws.ext.ds;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.aws.ext.S3Constants;
+import org.apache.jackrabbit.core.data.CachingDataStore;
+import org.apache.jackrabbit.core.data.DataRecord;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test S3DataStore operation with SSE_S3 encryption.
+ */
+public class TestS3DSWithSSES3 extends TestS3Ds {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(TestS3DSWithSSES3.class);
+
+    public TestS3DSWithSSES3() throws IOException {
+    }
+
+    @Override
+    protected CachingDataStore createDataStore() throws RepositoryException {
+        props.setProperty(S3Constants.S3_ENCRYPTION,
+            S3Constants.S3_ENCRYPTION_SSE_S3);
+        S3DataStore s3ds = new S3DataStore();
+        s3ds.setProperties(props);
+        s3ds.setSecret("123456");
+        s3ds.init(dataStoreDir);
+        sleep(1000);
+        return s3ds;
+    }
+
+    /**
+     * Test data migration enabling SSE_S3 encryption.
+     */
+    public void testDataMigration() {
+        try {
+            String bucket = props.getProperty(S3Constants.S3_BUCKET);
+            S3DataStore s3ds = new S3DataStore();
+            s3ds.setProperties(props);
+            s3ds.setCacheSize(0);
+            s3ds.init(dataStoreDir);
+            byte[] data = new byte[dataLength];
+            randomGen.nextBytes(data);
+            DataRecord rec = s3ds.addRecord(new ByteArrayInputStream(data));
+            assertEquals(data.length, rec.getLength());
+            assertRecord(data, rec);
+            s3ds.close();
+
+            // turn encryption now.
+            props.setProperty(S3Constants.S3_BUCKET, bucket);
+            props.setProperty(S3Constants.S3_ENCRYPTION,
+                S3Constants.S3_ENCRYPTION_SSE_S3);
+            props.setProperty(S3Constants.S3_RENAME_KEYS, "true");
+            s3ds = new S3DataStore();
+            s3ds.setProperties(props);
+            s3ds.setCacheSize(0);
+            s3ds.init(dataStoreDir);
+
+            rec = s3ds.getRecord(rec.getIdentifier());
+            assertEquals(data.length, rec.getLength());
+            assertRecord(data, rec);
+
+            randomGen.nextBytes(data);
+            rec = s3ds.addRecord(new ByteArrayInputStream(data));
+            s3ds.close();
+
+        } catch (Exception e) {
+            LOG.error("error:", e);
+            fail(e.getMessage());
+        }
+    }
+
+}
diff --git a/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3DSWithSmallCache.java b/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3DSWithSmallCache.java
new file mode 100644
index 0000000..b63bf67
--- /dev/null
+++ b/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3DSWithSmallCache.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.aws.ext.ds;
+
+import java.io.IOException;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.core.data.CachingDataStore;
+import org.apache.jackrabbit.core.data.LocalCache;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test {@link CachingDataStore} with S3Backend and with very small size (@link
+ * {@link LocalCache}. It requires to pass aws config file via system property.
+ * For e.g. -Dconfig=/opt/cq/aws.properties. Sample aws properties located at
+ * src/test/resources/aws.properties
+ */
+public class TestS3DSWithSmallCache extends TestS3Ds {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(TestS3DSWithSmallCache.class);
+
+    public TestS3DSWithSmallCache() throws IOException {
+    }
+
+    @Override
+    protected CachingDataStore createDataStore() throws RepositoryException {
+        S3DataStore s3ds = new S3DataStore();
+        s3ds.setProperties(props);
+        s3ds.setCacheSize(dataLength * 10);
+        s3ds.setCachePurgeTrigFactor(0.5d);
+        s3ds.setCachePurgeResizeFactor(0.4d);
+        s3ds.setSecret("123456");
+        s3ds.init(dataStoreDir);
+        sleep(1000);
+        return s3ds;
+    }
+}
diff --git a/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3Ds.java b/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3Ds.java
new file mode 100644
index 0000000..36c256c
--- /dev/null
+++ b/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3Ds.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.aws.ext.ds;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Properties;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.aws.ext.S3Constants;
+import org.apache.jackrabbit.aws.ext.Utils;
+import org.apache.jackrabbit.core.data.Backend;
+import org.apache.jackrabbit.core.data.CachingDataStore;
+import org.apache.jackrabbit.core.data.TestCaseBase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.amazonaws.services.s3.AmazonS3Client;
+import com.amazonaws.services.s3.model.DeleteObjectsRequest;
+import com.amazonaws.services.s3.model.ObjectListing;
+import com.amazonaws.services.s3.model.S3ObjectSummary;
+import com.amazonaws.services.s3.transfer.TransferManager;
+
+/**
+ * Test {@link CachingDataStore} with S3Backend and local cache on. It requires
+ * to pass aws config file via system property. For e.g.
+ * -Dconfig=/opt/cq/aws.properties. Sample aws properties located at
+ * src/test/resources/aws.properties
+ */
+public class TestS3Ds extends TestCaseBase {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(TestS3Ds.class);
+
+    private Date startTime = null;
+
+    protected Properties props;
+
+    protected String config;
+
+    public TestS3Ds() throws IOException {
+        System.setProperty(
+            TestCaseBase.CONFIG,
+            "C:/src/apache/jackrabbit-encryp-changes/jackrabbit/jackrabbit-aws-ext/src/test/resources/aws.properties");
+        config = System.getProperty(CONFIG);
+        props = Utils.readConfig(System.getProperty(CONFIG));
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        startTime = new Date();
+        super.setUp();
+        String bucket = String.valueOf(randomGen.nextInt(9999)) + "-"
+            + String.valueOf(randomGen.nextInt(9999)) + "-test";
+        props.setProperty(S3Constants.S3_BUCKET, bucket);
+        // delete bucket if exists
+        deleteBucket(bucket);
+    }
+
+    @Override
+    protected void tearDown() {
+        try {
+            deleteBucket();
+            super.tearDown();
+        } catch (Exception ignore) {
+
+        }
+    }
+
+    @Override
+    protected CachingDataStore createDataStore() throws RepositoryException {
+        S3DataStore s3ds = new S3DataStore();
+        s3ds.setProperties(props);
+        s3ds.setSecret("123456");
+        s3ds.init(dataStoreDir);
+        sleep(1000);
+        return s3ds;
+    }
+
+    /**
+     * Cleaning of bucket after test run.
+     */
+    /**
+     * Cleaning of bucket after test run.
+     */
+    public void deleteBucket() throws Exception {
+        Backend backend = ((S3DataStore) ds).getBackend();
+        String bucket = ((S3Backend) backend).getBucket();
+        deleteBucket(bucket);
+    }
+
+    public void deleteBucket(String bucket) throws Exception {
+        LOG.info("deleting bucket [" + bucket + "]");
+        Properties props = Utils.readConfig(config);
+        AmazonS3Client s3service = Utils.openService(props);
+        TransferManager tmx = new TransferManager(s3service);
+
+        if (s3service.doesBucketExist(bucket)) {
+            for (int i = 0; i < 4; i++) {
+                tmx.abortMultipartUploads(bucket, startTime);
+                ObjectListing prevObjectListing = s3service.listObjects(bucket);
+                while (prevObjectListing != null) {
+                    List<DeleteObjectsRequest.KeyVersion> deleteList = new ArrayList<DeleteObjectsRequest.KeyVersion>();
+                    for (S3ObjectSummary s3ObjSumm : prevObjectListing.getObjectSummaries()) {
+                        deleteList.add(new DeleteObjectsRequest.KeyVersion(
+                            s3ObjSumm.getKey()));
+                    }
+                    if (deleteList.size() > 0) {
+                        DeleteObjectsRequest delObjsReq = new DeleteObjectsRequest(
+                            bucket);
+                        delObjsReq.setKeys(deleteList);
+                        s3service.deleteObjects(delObjsReq);
+                    }
+                    if (!prevObjectListing.isTruncated()) break;
+                    prevObjectListing = s3service.listNextBatchOfObjects(prevObjectListing);
+                }
+            }
+            s3service.deleteBucket(bucket);
+            LOG.info("bucket [ " + bucket + "] deleted");
+
+        } else {
+            LOG.info("bucket [" + bucket + "] doesn't exists");
+        }
+        tmx.shutdownNow();
+        s3service.shutdown();
+    }
+
+}
diff --git a/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3DsCacheOff.java b/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3DsCacheOff.java
new file mode 100644
index 0000000..14896b9
--- /dev/null
+++ b/jackrabbit-aws-ext/src/test/java/org/apache/jackrabbit/aws/ext/ds/TestS3DsCacheOff.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.aws.ext.ds;
+
+import java.io.IOException;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.core.data.CachingDataStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test {@link CachingDataStore} with S3Backend and local cache Off. It requires
+ * to pass aws config file via system property. For e.g.
+ * -Dconfig=/opt/cq/aws.properties. Sample aws properties located at
+ * src/test/resources/aws.properties
+ */
+public class TestS3DsCacheOff extends TestS3Ds {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(TestS3DsCacheOff.class);
+
+    public TestS3DsCacheOff() throws IOException {
+    }
+
+    @Override
+    protected CachingDataStore createDataStore() throws RepositoryException {
+        S3DataStore s3ds = new S3DataStore();
+        s3ds.setProperties(props);
+        s3ds.setCacheSize(0);
+        s3ds.setSecret("123456");
+        s3ds.init(dataStoreDir);
+        sleep(1000);
+        return s3ds;
+    }
+}
diff --git a/jackrabbit-aws-ext/src/test/resources/aws.properties b/jackrabbit-aws-ext/src/test/resources/aws.properties
new file mode 100644
index 0000000..cddeb72
--- /dev/null
+++ b/jackrabbit-aws-ext/src/test/resources/aws.properties
@@ -0,0 +1,45 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# AWS account ID
+accessKey=
+# AWS secret key
+secretKey=
+# AWS bucket name
+s3Bucket=
+# AWS bucket region
+# Mapping of S3 regions to their constants
+# US Standard us-standard
+# US West us-west-2
+# US West (Northern California) us-west-1
+# EU (Ireland) EU
+# Asia Pacific (Singapore) ap-southeast-1
+# Asia Pacific (Sydney) ap-southeast-2
+# Asia Pacific (Tokyo) ap-northeast-1
+# South America (Sao Paulo) sa-east-1
+s3Region=
+# S3 endpoint to be used. This parameter is optional
+# and has a higher precedence over endpoint derived
+# via S3 region.
+s3EndPoint=
+connectionTimeout=120000
+socketTimeout=120000
+maxConnections=20
+maxErrorRetry=10
+# maximum concurrent threads to write to S3.
+writeThreads=10
+
diff --git a/jackrabbit-aws-ext/src/test/resources/log4j.properties b/jackrabbit-aws-ext/src/test/resources/log4j.properties
new file mode 100644
index 0000000..ac7c3fc
--- /dev/null
+++ b/jackrabbit-aws-ext/src/test/resources/log4j.properties
@@ -0,0 +1,31 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# this is the log4j configuration for the JCR API tests
+log4j.rootLogger=INFO, file
+log4j.logger.org.apache.jackrabbit.core.data.CachingDataStore=ERROR
+log4j.logger.org.apache.jackrabbit.aws.ext.ds=INFO
+
+#log4j.logger.org.apache.jackrabbit.test=DEBUG
+
+# 'file' is set to be a FileAppender.
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.File=target/debug.log
+
+# 'file' uses PatternLayout.
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{dd.MM.yyyy HH:mm:ss} *%-5p* [%t] %c{1}: %m (%F, line %L)\n
diff --git a/jackrabbit-aws-ext/src/test/resources/repository_sample.xml b/jackrabbit-aws-ext/src/test/resources/repository_sample.xml
new file mode 100644
index 0000000..40a00ea
--- /dev/null
+++ b/jackrabbit-aws-ext/src/test/resources/repository_sample.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
+                            "http://jackrabbit.apache.org/dtd/repository-1.6.dtd">
+<!-- Example Repository Configuration File -->
+<Repository>
+    <!--
+        virtual file system where the repository stores global state
+        (e.g. registered namespaces, custom node types, etc.)
+    -->
+    <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+        <param name="path" value="${rep.home}/repository"/>
+    </FileSystem>
+
+    <!--
+        data store configuration
+    -->
+	<!--
+    <DataStore class="org.apache.jackrabbit.core.data.FileDataStore"/>
+	-->
+	<DataStore class="org.apache.jackrabbit.aws.ext.ds.S3DataStore">
+		<param name="config" value="${rep.home}/aws.properties"/>
+        <param name="secret" value="123456789"/>
+        <param name="minRecordLength " value="16384"/> 
+        <param name="cacheSize" value="68719476736"/>
+        <param name="cachePurgeTrigFactor" value="0.95d"/>
+        <param name="cachePurgeResizeFactor" value="0.85d"/>
+        <param name="continueOnAsyncUploadFailure" value="false"/>
+        <param name="concurrentUploadsThreads" value="10"/>
+        <param name="asyncUploadLimit" value="100"/>
+        <param name="uploadRetries" value="3"/>
+      </DataStore>
+    <!--
+        sample database data store configuration
+        <DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
+            <param name="url" value="jdbc:h2:~/test"/>
+            <param name="user" value="sa"/>
+            <param name="password" value="sa"/>
+        </DataStore>
+    -->
+    
+    <!--
+        repository lock mechanism configuration
+    <RepositoryLockMechanism class="org.apache.jackrabbit.core.util.CooperativeFileLock"/>
+    -->
+
+    <!--
+        security configuration
+    -->
+    <Security appName="Jackrabbit">
+        <!--
+            security manager:
+            class: FQN of class implementing the JackrabbitSecurityManager interface
+        -->
+        <SecurityManager class="org.apache.jackrabbit.core.DefaultSecurityManager" workspaceName="security">
+            <!-- <param name="config" value="${rep.home}/security.xml"/> -->
+        </SecurityManager>
+
+        <!--
+            access manager:
+            class: FQN of class implementing the AccessManager interface
+        -->
+        <AccessManager class="org.apache.jackrabbit.core.security.DefaultAccessManager">
+            <!-- <param name="config" value="${rep.home}/access.xml"/> -->
+        </AccessManager>
+
+        <LoginModule class="org.apache.jackrabbit.core.security.authentication.DefaultLoginModule">
+           <!-- 
+              anonymous user name ('anonymous' is the default value)
+            -->
+           <param name="anonymousId" value="anonymous"/>
+           <!--
+              administrator user id (default value if param is missing is 'admin')
+            -->
+           <param name="adminId" value="admin"/>
+           <!--
+              optional parameter 'principalProvider'.
+              the value refers to the class name of the PrincipalProvider implementation.
+           -->
+           <!-- <param name="principalProvider" value="..."/> -->
+        </LoginModule>
+    </Security>
+
+    <!--
+        location of workspaces root directory and name of default workspace
+    -->
+    <Workspaces rootPath="${rep.home}/workspaces" defaultWorkspace="default" maxIdleTime="2"/>
+    <!--
+        workspace configuration template:
+        used to create the initial workspace if there's no workspace yet
+    -->
+    <Workspace name="${wsp.name}">
+        <!--
+            virtual file system of the workspace:
+            class: FQN of class implementing the FileSystem interface
+        -->
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${wsp.home}"/>
+        </FileSystem>
+        <!--
+            persistence manager of the workspace:
+            class: FQN of class implementing the PersistenceManager interface
+        -->
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+          <param name="url" value="jdbc:derby:${wsp.home}/db;create=true"/>
+          <param name="schemaObjectPrefix" value="${wsp.name}_"/>
+        </PersistenceManager>
+        <!--
+            Search index and the file system it uses.
+            class: FQN of class implementing the QueryHandler interface
+        -->
+        <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+            <param name="path" value="${wsp.home}/index"/>
+        </SearchIndex>
+    </Workspace>
+
+    <!--
+        Configures the versioning
+    -->
+    <Versioning rootPath="${rep.home}/version">
+        <!--
+            Configures the filesystem to use for versioning for the respective
+            persistence manager
+        -->
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${rep.home}/version" />
+        </FileSystem>
+
+        <!--
+            Configures the persistence manager to be used for persisting version state.
+            Please note that the current versioning implementation is based on
+            a 'normal' persistence manager, but this could change in future
+            implementations.
+        -->
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+          <param name="url" value="jdbc:derby:${rep.home}/version/db;create=true"/>
+          <param name="schemaObjectPrefix" value="version_"/>
+        </PersistenceManager>
+    </Versioning>
+
+    <!--
+        Search index for content that is shared repository wide
+        (/jcr:system tree, contains mainly versions)
+    -->
+    <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+        <param name="path" value="${rep.home}/repository/index"/>
+    </SearchIndex>
+    
+    <!--
+        Run with a cluster journal
+    -->
+    <Cluster id="node1">
+        <Journal class="org.apache.jackrabbit.core.journal.MemoryJournal"/>
+    </Cluster>
+</Repository>
diff --git a/jackrabbit-bundle/pom.xml b/jackrabbit-bundle/pom.xml
index 42ceb7f..064d555 100644
--- a/jackrabbit-bundle/pom.xml
+++ b/jackrabbit-bundle/pom.xml
@@ -26,7 +26,7 @@
   <parent>
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>jackrabbit-parent</artifactId>
-    <version>2.3.6</version>
+    <version>2.10.1</version>
     <relativePath>../jackrabbit-parent/pom.xml</relativePath>
   </parent>
   <artifactId>jackrabbit-bundle</artifactId>
@@ -93,7 +93,7 @@
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-core</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <scope>provided</scope>
       <exclusions>
         <exclusion>
@@ -113,7 +113,7 @@
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr2dav</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <scope>provided</scope>
       <exclusions>
         <exclusion>
diff --git a/jackrabbit-core/assembly.xml b/jackrabbit-core/assembly.xml
deleted file mode 100644
index 258992e..0000000
--- a/jackrabbit-core/assembly.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-  -->
-<assembly>
-  <id>tests</id>
-  <includeBaseDirectory>false</includeBaseDirectory>
-  <formats>
-    <format>jar</format>
-  </formats>
-  <fileSets>
-    <fileSet>
-      <directory>${project.build.testOutputDirectory}</directory>
-      <outputDirectory></outputDirectory>
-    </fileSet>
-  </fileSets>
-</assembly>
diff --git a/jackrabbit-core/pom.xml b/jackrabbit-core/pom.xml
index 8e3c4cc..584d462 100644
--- a/jackrabbit-core/pom.xml
+++ b/jackrabbit-core/pom.xml
@@ -26,7 +26,7 @@
   <parent>
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>jackrabbit-parent</artifactId>
-    <version>2.3.6</version>
+    <version>2.10.1</version>
     <relativePath>../jackrabbit-parent/pom.xml</relativePath>
   </parent>
   <artifactId>jackrabbit-core</artifactId>
@@ -74,7 +74,7 @@
           <includes>
             <include>**/*TestAll.java</include>
           </includes>
-          <argLine>-Xmx256m</argLine>
+          <argLine>-Xmx512m</argLine>
           <systemProperties>
             <property>
               <name>java.awt.headless</name>
@@ -99,6 +99,7 @@
             <property>
               <name>known.issues</name>
               <value>
+org.apache.jackrabbit.core.ConcurrentImportTest
 org.apache.jackrabbit.core.xml.DocumentViewTest#testMultiValue
 org.apache.jackrabbit.core.NodeImplTest#testReferentialIntegrityCorruptionGetPath
 org.apache.jackrabbit.core.integration.ConcurrentQueryTest#testConcurrentQueryWithDeletes
@@ -106,10 +107,14 @@ org.apache.jackrabbit.test.api.ShareableNodeTest#testGetName
 org.apache.jackrabbit.test.api.ShareableNodeTest#testGetNode
 org.apache.jackrabbit.test.api.ShareableNodeTest#testGetNodes
 org.apache.jackrabbit.test.api.ShareableNodeTest#testGetNodesByPattern
+org.apache.jackrabbit.test.api.ShareableNodeTest#testMoveShareableNode<!--JCR-3381-->
+org.apache.jackrabbit.test.api.ShareableNodeTest#testTransientMoveShareableNode<!--JCR-3381-->
 org.apache.jackrabbit.test.api.lock.OpenScopedLockTest#testLockExpiration
 org.apache.jackrabbit.test.api.lock.SessionScopedLockTest#testLockExpiration
+org.apache.jackrabbit.test.api.observation.NodeReorderTest#testNodeReorderMove
 org.apache.jackrabbit.core.data.ConcurrentGcTest#testDatabases
 org.apache.jackrabbit.core.data.GarbageCollectorTest#testCloseSessionWhileRunningGc
+org.apache.jackrabbit.core.ReplacePropertyWhileOthersReadTest <!-- JCR-3835 -->
               </value>
             </property>
             <property>
@@ -151,22 +156,23 @@ org.apache.jackrabbit.core.data.GarbageCollectorTest#testCloseSessionWhileRunnin
           </excludes>
         </configuration>
       </plugin>
-      <plugin>
-        <artifactId>maven-assembly-plugin</artifactId>
-        <executions>
-          <execution>
-            <phase>package</phase>
-            <goals>
-              <goal>single</goal>
-            </goals>
-            <configuration>
-              <descriptors>
-                <descriptor>assembly.xml</descriptor>
-              </descriptors>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
+     <plugin>
+       <groupId>org.apache.maven.plugins</groupId>
+       <artifactId>maven-jar-plugin</artifactId>
+       <version>2.4</version>
+       <configuration>
+          <excludes>
+            <exclude>logback-test.xml</exclude>
+          </excludes>
+       </configuration>
+       <executions>
+         <execution>
+           <goals>
+             <goal>test-jar</goal>
+           </goals>
+         </execution>
+       </executions>
+     </plugin>
     </plugins>
     <resources>
       <resource>
@@ -224,7 +230,7 @@ org.apache.jackrabbit.core.data.GarbageCollectorTest#testCloseSessionWhileRunnin
     <dependency>
       <groupId>commons-dbcp</groupId>
       <artifactId>commons-dbcp</artifactId>
-      <version>1.2.2</version>
+      <version>1.3</version>
     </dependency>
     <dependency>
       <groupId>javax.jcr</groupId>
@@ -233,28 +239,40 @@ org.apache.jackrabbit.core.data.GarbageCollectorTest#testCloseSessionWhileRunnin
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-api</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-commons</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
+      <artifactId>jackrabbit-data</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+        <groupId>org.apache.jackrabbit</groupId>
+        <artifactId>jackrabbit-data</artifactId>
+        <version>${project.version}</version>
+        <type>test-jar</type>
+        <scope>test</scope>
+    </dependency>  
+    <dependency>
+      <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-spi-commons</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-spi</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <classifier />
     </dependency>
       <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-spi</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <classifier>tests</classifier>
       <scope>test</scope>
     </dependency>
@@ -277,7 +295,7 @@ org.apache.jackrabbit.core.data.GarbageCollectorTest#testCloseSessionWhileRunnin
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-tests</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <optional>true</optional>
     </dependency>
     <dependency>
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java
index 664b749..ad7e3fd 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/BatchedItemOperations.java
@@ -311,7 +311,7 @@ public class BatchedItemOperations extends ItemValidator {
     /**
      * Copies the tree at <code>srcPath</code> to the new location at
      * <code>destPath</code>. Returns the id of the node at its new position.
-     * <p/>
+     * <p>
      * <b>Precondition:</b> the state manager needs to be in edit mode.
      *
      * @param srcPath
@@ -336,7 +336,7 @@ public class BatchedItemOperations extends ItemValidator {
      * Copies the tree at <code>srcPath</code> retrieved using the specified
      * <code>srcStateMgr</code> to the new location at <code>destPath</code>.
      * Returns the id of the node at its new position.
-     * <p/>
+     * <p>
      * <b>Precondition:</b> the state manager needs to be in edit mode.
      *
      * @param srcPath
@@ -438,7 +438,8 @@ public class BatchedItemOperations extends ItemValidator {
             for (int i = 0; i < values.length; i++) {
                 NodeId adjusted = refTracker.getMappedId(values[i].getNodeId());
                 if (adjusted != null) {
-                    newVals[i] = InternalValue.create(adjusted);
+                    boolean weak = prop.getType() == PropertyType.WEAKREFERENCE;
+                    newVals[i] = InternalValue.create(adjusted, weak);
                     modified = true;
                 } else {
                     // reference doesn't need adjusting, just copy old value
@@ -461,7 +462,7 @@ public class BatchedItemOperations extends ItemValidator {
     /**
      * Moves the tree at <code>srcPath</code> to the new location at
      * <code>destPath</code>. Returns the id of the moved node.
-     * <p/>
+     * <p>
      * <b>Precondition:</b> the state manager needs to be in edit mode.
      *
      * @param srcPath
@@ -547,7 +548,9 @@ public class BatchedItemOperations extends ItemValidator {
             srcNameIndex = 1;
         }
 
+        stateMgr.store(target);
         if (renameOnly) {
+            stateMgr.store(srcParent);
             // change child node entry
             destParent.renameChildNodeEntry(srcPath.getName(), srcNameIndex,
                     destPath.getName());
@@ -561,6 +564,9 @@ public class BatchedItemOperations extends ItemValidator {
                 throw new UnsupportedRepositoryOperationException(msg);
             }
 
+            stateMgr.store(srcParent);
+            stateMgr.store(destParent);
+
             // do move:
             // 1. remove child node entry from old parent
             if (srcParent.removeChildNodeEntry(target.getNodeId())) {
@@ -571,21 +577,13 @@ public class BatchedItemOperations extends ItemValidator {
             }
         }
 
-        // store states
-        stateMgr.store(target);
-        if (renameOnly) {
-            stateMgr.store(srcParent);
-        } else {
-            stateMgr.store(destParent);
-            stateMgr.store(srcParent);
-        }
         return target.getNodeId();
     }
 
     /**
      * Removes the specified node, recursively removing its properties and
      * child nodes.
-     * <p/>
+     * <p>
      * <b>Precondition:</b> the state manager needs to be in edit mode.
      *
      * @param nodePath
@@ -1030,9 +1028,9 @@ public class BatchedItemOperations extends ItemValidator {
     //--------------------------------------------< low-level item operations >
     /**
      * Creates a new node.
-     * <p/>
+     * <p>
      * Note that access rights are <b><i>not</i></b> enforced!
-     * <p/>
+     * <p>
      * <b>Precondition:</b> the state manager needs to be in edit mode.
      *
      * @param parent
@@ -1067,9 +1065,9 @@ public class BatchedItemOperations extends ItemValidator {
 
     /**
      * Creates a new node based on the given definition.
-     * <p/>
+     * <p>
      * Note that access rights are <b><i>not</i></b> enforced!
-     * <p/>
+     * <p>
      * <b>Precondition:</b> the state manager needs to be in edit mode.
      *
      * @param parent
@@ -1153,9 +1151,9 @@ public class BatchedItemOperations extends ItemValidator {
 
     /**
      * Creates a new property.
-     * <p/>
+     * <p>
      * Note that access rights are <b><i>not</i></b> enforced!
-     * <p/>
+     * <p>
      * <b>Precondition:</b> the state manager needs to be in edit mode.
      *
      * @param parent
@@ -1206,9 +1204,9 @@ public class BatchedItemOperations extends ItemValidator {
 
     /**
      * Creates a new property based on the given definition.
-     * <p/>
+     * <p>
      * Note that access rights are <b><i>not</i></b> enforced!
-     * <p/>
+     * <p>
      * <b>Precondition:</b> the state manager needs to be in edit mode.
      *
      * @param parent
@@ -1258,7 +1256,7 @@ public class BatchedItemOperations extends ItemValidator {
     /**
      * Unlinks the specified node state from its parent and recursively
      * removes it including its properties and child nodes.
-     * <p/>
+     * <p>
      * Note that no checks (access rights etc.) are performed on the specified
      * target node state. Those checks have to be performed beforehand by the
      * caller. However, the (recursive) removal of target node's child nodes are
@@ -1287,7 +1285,7 @@ public class BatchedItemOperations extends ItemValidator {
 
     /**
      * Retrieves the state of the node at the given path.
-     * <p/>
+     * <p>
      * Note that access rights are <b><i>not</i></b> enforced!
      *
      * @param nodePath
@@ -1302,7 +1300,7 @@ public class BatchedItemOperations extends ItemValidator {
 
     /**
      * Retrieves the state of the node with the given id.
-     * <p/>
+     * <p>
      * Note that access rights are <b><i>not</i></b> enforced!
      *
      * @param id
@@ -1317,7 +1315,7 @@ public class BatchedItemOperations extends ItemValidator {
 
     /**
      * Retrieves the state of the property with the given id.
-     * <p/>
+     * <p>
      * Note that access rights are <b><i>not</i></b> enforced!
      *
      * @param id
@@ -1332,7 +1330,7 @@ public class BatchedItemOperations extends ItemValidator {
 
     /**
      * Retrieves the state of the item with the given id.
-     * <p/>
+     * <p>
      * Note that access rights are <b><i>not</i></b> enforced!
      *
      * @param id
@@ -1349,7 +1347,7 @@ public class BatchedItemOperations extends ItemValidator {
     /**
      * Verifies that the node at <code>nodePath</code> is checked-out; throws a
      * <code>VersionException</code> if that's not the case.
-     * <p/>
+     * <p>
      * A node is considered <i>checked-out</i> if it is versionable and
      * checked-out, or is non-versionable but its nearest versionable ancestor
      * is checked-out, or is non-versionable and there are no versionable
@@ -1431,7 +1429,7 @@ public class BatchedItemOperations extends ItemValidator {
     /**
      * Retrieves the state of the node at <code>nodePath</code> using the given
      * item state manager.
-     * <p/>
+     * <p>
      * Note that access rights are <b><i>not</i></b> enforced!
      *
      * @param srcStateMgr
@@ -1459,7 +1457,7 @@ public class BatchedItemOperations extends ItemValidator {
     /**
      * Retrieves the state of the item with the specified id using the given
      * item state manager.
-     * <p/>
+     * <p>
      * Note that access rights are <b><i>not</i></b> enforced!
      *
      * @param srcStateMgr
@@ -1487,11 +1485,11 @@ public class BatchedItemOperations extends ItemValidator {
     /**
      * Recursively removes the given node state including its properties and
      * child nodes.
-     * <p/>
+     * <p>
      * The removal of child nodes is subject to the following checks:
      * access rights, locking & versioning status. Referential integrity
      * (references) is checked on commit.
-     * <p/>
+     * <p>
      * Note that the child node entry refering to <code>targetState</code> is
      * <b><i>not</i></b> automatically removed from <code>targetState</code>'s
      * parent.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java
index 72dc201..5f9f3e0 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/CachingHierarchyManager.java
@@ -97,6 +97,16 @@ public class CachingHierarchyManager extends HierarchyManagerImpl
     private boolean consistencyCheckEnabled;
 
     /**
+     * Log interval for item state exceptions.
+     */
+    private static final int ITEM_STATE_EXCEPTION_LOG_INTERVAL_MILLIS = 60 * 1000;
+
+    /**
+     * Last time-stamp item state exception was logged with a stacktrace. 
+     */
+    private long itemStateExceptionLogTimestamp = 0;
+
+    /**
      * Create a new instance of this class.
      *
      * @param rootNodeId   root node id
@@ -151,10 +161,15 @@ public class CachingHierarchyManager extends HierarchyManagerImpl
         try {
             return resolvePath(elements, element.getDepth() + 1, entry.getId(), typesAllowed);
         } catch (ItemStateException e) {
-            String msg = "failed to retrieve state of intermediary node";
+            String msg = "failed to retrieve state of intermediary node for entry: " 
+                    + entry.getId() + ", path: " + path.getString();
+            logItemStateException(msg, e);
             log.debug(msg);
-            throw new RepositoryException(msg, e);
+            // probably stale cache entry -> evict
+            evictAll(entry.getId(), true);
         }
+        // JCR-3617: fall back to super class in case of ItemStateException
+        return super.resolvePath(path, typesAllowed);
     }
 
     /**
@@ -170,13 +185,14 @@ public class CachingHierarchyManager extends HierarchyManagerImpl
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Overridden method tries to find a mapping for the intermediate item
      * <code>state</code> and add its path elements to the builder currently
      * being used. If no mapping is found, the item is cached instead after
      * the base implementation has been invoked.
      */
-    protected void buildPath(PathBuilder builder, ItemState state)
+    protected void buildPath(
+            PathBuilder builder, ItemState state, CycleDetector detector)
             throws ItemStateException, RepositoryException {
 
         if (state.isNode()) {
@@ -196,7 +212,7 @@ public class CachingHierarchyManager extends HierarchyManagerImpl
             }
         }
 
-        super.buildPath(builder, state);
+        super.buildPath(builder, state, detector);
 
         if (state.isNode()) {
             try {
@@ -211,7 +227,7 @@ public class CachingHierarchyManager extends HierarchyManagerImpl
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Overridden method simply checks whether we have an item matching the id
      * and returns its path, otherwise calls base implementation.
      */
@@ -370,9 +386,9 @@ public class CachingHierarchyManager extends HierarchyManagerImpl
      * {@inheritDoc}
      */
     public void nodeAdded(NodeState state, Name name, int index, NodeId id) {
-        // Optimization: ignore notifications for nodes that are not in the cache
         synchronized (cacheMonitor) {
             if (idCache.containsKey(state.getNodeId())) {
+                // Optimization: ignore notifications for nodes that are not in the cache
                 try {
                     Path path = PathFactoryImpl.getInstance().create(getPath(state.getNodeId()), name, index, true);
                     nodeAdded(state, path, id);
@@ -389,13 +405,16 @@ public class CachingHierarchyManager extends HierarchyManagerImpl
                 } catch (RepositoryException e) {
                     log.warn("Unable to get path of " + state.getNodeId(), e);
                 }
+            } else if (state.getParentId() == null && idCache.containsKey(id)) {
+                // A top level node was added
+                evictAll(id, true);
             }
         }
     }
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Iterate over all cached children of this state and verify each
      * child's position.
      */
@@ -453,9 +472,9 @@ public class CachingHierarchyManager extends HierarchyManagerImpl
      * {@inheritDoc}
      */
     public void nodeRemoved(NodeState state, Name name, int index, NodeId id) {
-        // Optimization: ignore notifications for nodes that are not in the cache
         synchronized (cacheMonitor) {
             if (idCache.containsKey(state.getNodeId())) {
+                // Optimization: ignore notifications for nodes that are not in the cache
                 try {
                     Path path = PathFactoryImpl.getInstance().create(getPath(state.getNodeId()), name, index, true);
                     nodeRemoved(state, path, id);
@@ -472,6 +491,9 @@ public class CachingHierarchyManager extends HierarchyManagerImpl
                 } catch (RepositoryException e) {
                     log.warn("Unable to get path of " + state.getNodeId(), e);
                 }
+            } else if (state.getParentId() == null && idCache.containsKey(id)) {
+                // A top level node was removed
+                evictAll(id, true);
             }
         }
     }
@@ -551,7 +573,7 @@ public class CachingHierarchyManager extends HierarchyManagerImpl
             PathMap.Element<LRUEntry> element = pathCache.put(path);
             if (element.get() != null) {
                 if (!id.equals(((LRUEntry) element.get()).getId())) {
-                    log.warn("overwriting PathMap.Element");
+                    log.debug("overwriting PathMap.Element");
                 }
             }
             LRUEntry entry = (LRUEntry) idCache.get(id);
@@ -833,6 +855,22 @@ public class CachingHierarchyManager extends HierarchyManagerImpl
     }
 
     /**
+     * Helper method to log item state exception with stack trace every so often.
+     * 
+     * @param logMessage log message
+     * @param e item state exception
+     */
+    private void logItemStateException(String logMessage, ItemStateException e) {
+        long now = System.currentTimeMillis();
+        if ((now - itemStateExceptionLogTimestamp) >= ITEM_STATE_EXCEPTION_LOG_INTERVAL_MILLIS) {
+            itemStateExceptionLogTimestamp = now;
+            log.debug(logMessage, e);
+        } else {
+            log.debug(logMessage);
+        }
+    }
+
+    /**
      * Entry in the LRU list
      */
     private class LRUEntry {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java
index f5fcaa4..955a0f3 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java
@@ -49,12 +49,14 @@ import org.apache.jackrabbit.core.security.AccessManager;
 import org.apache.jackrabbit.core.security.DefaultAccessManager;
 import org.apache.jackrabbit.core.security.JackrabbitSecurityManager;
 import org.apache.jackrabbit.core.security.SecurityConstants;
+import org.apache.jackrabbit.core.security.SystemPrincipal;
 import org.apache.jackrabbit.core.security.authentication.AuthContext;
 import org.apache.jackrabbit.core.security.authentication.AuthContextProvider;
 import org.apache.jackrabbit.core.security.authorization.AccessControlProvider;
 import org.apache.jackrabbit.core.security.authorization.AccessControlProviderFactory;
 import org.apache.jackrabbit.core.security.authorization.AccessControlProviderFactoryImpl;
 import org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager;
+import org.apache.jackrabbit.core.security.principal.AbstractPrincipalProvider;
 import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
 import org.apache.jackrabbit.core.security.principal.DefaultPrincipalProvider;
 import org.apache.jackrabbit.core.security.principal.PrincipalManagerImpl;
@@ -221,7 +223,7 @@ public class DefaultSecurityManager implements JackrabbitSecurityManager {
 
         // initialize principal-provider registry
         // 1) create default
-        PrincipalProvider defaultPP = createDefaultPrincipalProvider();
+        PrincipalProvider defaultPP = createDefaultPrincipalProvider(moduleConfig);
         // 2) create registry instance
         principalProviderRegistry = new ProviderRegistryImpl(defaultPP);
         // 3) register all configured principal providers.
@@ -336,11 +338,15 @@ public class DefaultSecurityManager implements JackrabbitSecurityManager {
     public String getUserID(Subject subject, String workspaceName) throws RepositoryException {
         checkInitialized();
 
-        /* shortcut if the subject contains the AdminPrincipal in which case
-           the userID is already known. */
+        // shortcut if the subject contains the AdminPrincipal or
+        // SystemPrincipal in which cases the userID is already known.
         if (!subject.getPrincipals(AdminPrincipal.class).isEmpty()) {
             return adminId;
+        } else if (!subject.getPrincipals(SystemPrincipal.class).isEmpty()) {
+            // system session does not have a userId
+            return null;
         }
+
         /* if there is a configure principal class that should be used to
            determine the UserID -> try this one. */
         Class cl = getConfig().getUserIdClass();
@@ -462,12 +468,6 @@ public class DefaultSecurityManager implements JackrabbitSecurityManager {
      */
     protected UserManagerImpl createUserManager(SessionImpl session) throws RepositoryException {
         UserManagerConfig umc = getConfig().getUserManagerConfig();
-        Properties params = (umc == null) ? null : umc.getParameters();
-
-        // since users are stored in and retrieved from a dedicated workspace
-        // only the system session assigned with that workspace will get the
-        // system user manager (special implementation that asserts the existence
-        // of the admin user).
         UserManagerImpl um;
         if (umc != null) {
             Class<?>[] paramTypes = new Class[] {
@@ -476,12 +476,9 @@ public class DefaultSecurityManager implements JackrabbitSecurityManager {
                     Properties.class,
                     MembershipCache.class};
             um = (UserManagerImpl) umc.getUserManager(UserManagerImpl.class,
-                    paramTypes, session, adminId, params, getMembershipCache(session));
-            // TODO: should we make sure the implementation doesn't allow
-            // TODO: to change the autosave behavior? since the user manager
-            // TODO: writes to a separate workspace this would cause troubles.
+                    paramTypes, session, adminId, umc.getParameters(), getMembershipCache(session));
         } else {
-            um = new UserManagerImpl(session, adminId, params, getMembershipCache(session));
+            um = new UserManagerImpl(session, adminId, null, getMembershipCache(session));
         }
 
         if (umc != null && !(session instanceof SystemSession)) {
@@ -515,9 +512,20 @@ public class DefaultSecurityManager implements JackrabbitSecurityManager {
      * @return An new instance of <code>DefaultPrincipalProvider</code>.
      * @throws RepositoryException If an error occurs.
      */
-    protected PrincipalProvider createDefaultPrincipalProvider() throws RepositoryException {
+    protected PrincipalProvider createDefaultPrincipalProvider(Properties[] moduleConfig) throws RepositoryException {
+        boolean initialized = false;
         PrincipalProvider defaultPP = new DefaultPrincipalProvider(this.systemSession, (UserManagerImpl) systemUserManager);
-        defaultPP.init(new Properties());
+        for (Properties props : moduleConfig) {
+            //GRANITE-4470: apply config to DefaultPrincipalProvider if there is no explicit PrincipalProvider configured
+            if (!props.containsKey(LoginModuleConfig.PARAM_PRINCIPAL_PROVIDER_CLASS) && props.containsKey(AbstractPrincipalProvider.MAXSIZE_KEY)) {
+                defaultPP.init(props);
+                initialized = true;
+                break;
+            }
+        }
+        if (!initialized) {
+            defaultPP.init(new Properties());
+        }
         return defaultPP;
     }
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.java
index 2668002..9daaed6 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManager.java
@@ -32,10 +32,10 @@ public interface HierarchyManager {
 
     /**
      * Resolves a path into an item id.
-     * <p/>
+     * <p>
      * If there is both a node and a property at the specified path, this method
      * will return the id of the node.
-     * <p/>
+     * <p>
      * Note that, for performance reasons, this method returns <code>null</code>
      * rather than throwing a <code>PathNotFoundException</code> if there's no
      * item to be found at <code>path</code>.
@@ -53,7 +53,7 @@ public interface HierarchyManager {
 
     /**
      * Resolves a path into a node id.
-     * <p/>
+     * <p>
      * Note that, for performance reasons, this method returns <code>null</code>
      * rather than throwing a <code>PathNotFoundException</code> if there's no
      * node to be found at <code>path</code>.
@@ -67,7 +67,7 @@ public interface HierarchyManager {
 
     /**
      * Resolves a path into a property id.
-     * <p/>
+     * <p>
      * Note that, for performance reasons, this method returns <code>null</code>
      * rather than throwing a <code>PathNotFoundException</code> if there's no
      * property to be found at <code>path</code>.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java
index 5b50982..c4ccf3d 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/HierarchyManagerImpl.java
@@ -17,9 +17,11 @@
 package org.apache.jackrabbit.core;
 
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.Set;
 
+import javax.jcr.InvalidItemStateException;
 import javax.jcr.ItemNotFoundException;
 import javax.jcr.RepositoryException;
 
@@ -138,7 +140,7 @@ public class HierarchyManagerImpl implements HierarchyManager {
     //---------------------------------------------------------< overridables >
     /**
      * Return an item state, given its item id.
-     * <p/>
+     * <p>
      * Low-level hook provided for specialized derived classes.
      *
      * @param id item id
@@ -154,7 +156,7 @@ public class HierarchyManagerImpl implements HierarchyManager {
 
     /**
      * Determines whether an item state for a given item id exists.
-     * <p/>
+     * <p>
      * Low-level hook provided for specialized derived classes.
      *
      * @param id item id
@@ -168,7 +170,7 @@ public class HierarchyManagerImpl implements HierarchyManager {
 
     /**
      * Returns the <code>parentUUID</code> of the given item.
-     * <p/>
+     * <p>
      * Low-level hook provided for specialized derived classes.
      *
      * @param state item state
@@ -213,7 +215,7 @@ public class HierarchyManagerImpl implements HierarchyManager {
     /**
      * Returns the <code>ChildNodeEntry</code> of <code>parent</code> with the
      * specified <code>uuid</code> or <code>null</code> if there's no such entry.
-     * <p/>
+     * <p>
      * Low-level hook provided for specialized derived classes.
      *
      * @param parent node state
@@ -232,7 +234,7 @@ public class HierarchyManagerImpl implements HierarchyManager {
      * Returns the <code>ChildNodeEntry</code> of <code>parent</code> with the
      * specified <code>name</code> and <code>index</code> or <code>null</code>
      * if there's no such entry.
-     * <p/>
+     * <p>
      * Low-level hook provided for specialized derived classes.
      *
      * @param parent node state
@@ -255,10 +257,12 @@ public class HierarchyManagerImpl implements HierarchyManager {
      * either return cached responses or add response to cache. On exit,
      * <code>builder</code> contains the path of <code>state</code>.
      *
-     * @param builder builder currently being used
-     * @param state   item to find path of
+     * @param builder  builder currently being used
+     * @param state    item to find path of
+     * @param detector path cycle detector
      */
-    protected void buildPath(PathBuilder builder, ItemState state)
+    protected void buildPath(
+            PathBuilder builder, ItemState state, CycleDetector detector)
             throws ItemStateException, RepositoryException {
 
         // shortcut
@@ -273,11 +277,14 @@ public class HierarchyManagerImpl implements HierarchyManager {
                     + ": orphaned item";
             log.debug(msg);
             throw new ItemNotFoundException(msg);
+        } else if (detector.checkCycle(parentId)) {
+            throw new InvalidItemStateException(
+                    "Path cycle detected: " + parentId);
         }
 
         NodeState parent = (NodeState) getItemState(parentId);
         // recursively build path of parent
-        buildPath(builder, parent);
+        buildPath(builder, parent, detector);
 
         if (state.isNode()) {
             NodeState nodeState = (NodeState) state;
@@ -392,7 +399,7 @@ public class HierarchyManagerImpl implements HierarchyManager {
         PathBuilder builder = new PathBuilder();
 
         try {
-            buildPath(builder, getItemState(id));
+            buildPath(builder, getItemState(id), new CycleDetector());
             return builder.getPath();
         } catch (NoSuchItemStateException nsise) {
             String msg = "failed to build path of " + id;
@@ -644,4 +651,37 @@ public class HierarchyManagerImpl implements HierarchyManager {
             throw new RepositoryException(msg, ise);
         }
     }
+
+    /**
+     * Utility class used to detect path cycles with as little overhead
+     * as possible. The {@link #checkCycle(ItemId)} method is called for
+     * each path element as the
+     * {@link HierarchyManagerImpl#buildPath(PathBuilder, ItemState, CycleDetector)}
+     * method walks up the hierarchy. At first, during the first fifteen
+     * path elements, the detector does nothing in order to avoid
+     * introducing any unnecessary overhead to normal paths that seldom
+     * are deeper than that. After that initial threshold all item
+     * identifiers along the path are tracked, and a cycle is reported
+     * if an identifier is encountered that already occurred along the
+     * same path.
+     */
+    protected static class CycleDetector {
+
+        private int count = 0;
+
+        private Set<ItemId> ids;
+
+        boolean checkCycle(ItemId id) throws InvalidItemStateException {
+            if (count++ >= 15) {
+                if (ids == null) {
+                    ids = new HashSet<ItemId>();
+                } else {
+                    return !ids.add(id);
+                }
+            }
+            return false;
+        }
+
+    }
+
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/InternalXAResource.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/InternalXAResource.java
deleted file mode 100755
index 81fb259..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/InternalXAResource.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core;
-
-/**
- * Interface implemented by resources that provide XA functionality.
- */
-public interface InternalXAResource {
-
-    /**
-     * Associate this resource with a transaction. All further operations on
-     * the object should be interpreted as part of this transaction and changes
-     * recorded in some attribute of the transaction context.
-     * @param tx transaction context, if <code>null</code> disassociate
-     */
-    void associate(TransactionContext tx);
-
-    /**
-     * Invoked before one of the {@link #prepare}, {@link #commit} or
-     * {@link #rollback} method is called.
-     * @param tx transaction context
-     */
-    void beforeOperation(TransactionContext tx);
-
-    /**
-     * Prepare transaction. The transaction is identified by a transaction
-     * context.
-     * @param tx transaction context
-     * @throws TransactionException if an error occurs
-     */
-    void prepare(TransactionContext tx) throws TransactionException;
-
-    /**
-     * Commit transaction. The transaction is identified by a transaction
-     * context. If the method throws, other resources get their changes
-     * rolled back.
-     * @param tx transaction context
-     * @throws TransactionException if an error occurs
-     */
-    void commit(TransactionContext tx) throws TransactionException;
-
-    /**
-     * Rollback transaction. The transaction is identified by a transaction
-     * context.
-     * @param tx transaction context.
-     */
-    void rollback(TransactionContext tx) throws TransactionException;
-
-    /**
-     * Invoked after one of the {@link #prepare}, {@link #commit} or
-     * {@link #rollback} method has been called.
-     * @param tx transaction context
-     */
-    void afterOperation(TransactionContext tx);
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemLifeCycleListener.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemLifeCycleListener.java
index 104041d..9954ed3 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemLifeCycleListener.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemLifeCycleListener.java
@@ -34,7 +34,7 @@ public interface ItemLifeCycleListener {
     /**
      * Called when an <code>ItemImpl</code> instance has been invalidated
      * (i.e. it has been temporarily rendered 'invalid').
-     * <p/>
+     * <p>
      * Note that most <code>{@link javax.jcr.Item}</code>,
      * <code>{@link javax.jcr.Node}</code> and <code>{@link javax.jcr.Property}</code>
      * methods will throw an <code>InvalidItemStateException</code> when called
@@ -48,7 +48,7 @@ public interface ItemLifeCycleListener {
     /**
      * Called when an <code>ItemImpl</code> instance has been destroyed
      * (i.e. it has been permanently rendered 'invalid').
-     * <p/>
+     * <p>
      * Note that most <code>{@link javax.jcr.Item}</code>,
      * <code>{@link javax.jcr.Node}</code> and <code>{@link javax.jcr.Property}</code>
      * methods will throw an <code>InvalidItemStateException</code> when called
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java
index 5ba6603..e176edf 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemManager.java
@@ -64,7 +64,7 @@ import org.slf4j.LoggerFactory;
  * There's one <code>ItemManager</code> instance per <code>Session</code>
  * instance. It is the factory for <code>Node</code> and <code>Property</code>
  * instances.
- * <p/>
+ * <p>
  * The <code>ItemManager</code>'s responsibilities are:
  * <ul>
  * <li>providing access to <code>Item</code> instances by <code>ItemId</code>
@@ -79,7 +79,7 @@ import org.slf4j.LoggerFactory;
  * <li>maintaining a cache of the item instances it created.
  * <li>respecting access rights of associated <code>Session</code> in all methods.
  * </ul>
- * <p/>
+ * <p>
  * If the parent <code>Session</code> is an <code>XASession</code>, there is
  * one <code>ItemManager</code> instance per started global transaction.
  */
@@ -667,6 +667,11 @@ public class ItemManager implements ItemStateListener {
         AbstractNodeData data = retrieveItem(id, parentId);
         if (data == null) {
             data = (AbstractNodeData) getItemData(id, null, permissionCheck);
+        } else if (permissionCheck && !canRead(data, id)) {
+            // item exists but read-perm has been revoked in the mean time.
+            // -> remove from cache
+            evictItems(id);
+            throw new AccessDeniedException("cannot read item " + data.getId());
         }
         if (!data.getParentId().equals(parentId)) {
             // verify that parent actually appears in the shared set
@@ -928,7 +933,7 @@ public class ItemManager implements ItemStateListener {
             }
             ItemId id = data.getId();
             if (itemCache.containsKey(id)) {
-                log.warn("overwriting cached item " + id);
+                log.debug("overwriting cached item " + id);
             }
             if (log.isDebugEnabled()) {
                 log.debug("caching item " + id);
@@ -1256,7 +1261,7 @@ public class ItemManager implements ItemStateListener {
             }
             Object old = map.put(data.getPrimaryParentId(), data);
             if (old != null) {
-                log.warn("overwriting cached item: " + old);
+                log.debug("overwriting cached item: " + old);
             }
         }
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java
index 9499aba..26a84e5 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ItemSaveOperation.java
@@ -712,13 +712,16 @@ class ItemSaveOperation implements SessionWriteOperation<Object> {
      * definitively remove each one
      */
     private void removeTransientItems(
-            SessionItemStateManager sism, Iterable<ItemState> states) {
+            SessionItemStateManager sism, Iterable<ItemState> states) throws StaleItemStateException {
         for (ItemState transientState : states) {
             ItemState persistentState = transientState.getOverlayedState();
             // remove persistent state
             // this will indirectly (through stateDestroyed listener method)
             // permanently invalidate all Item instances wrapping it
             assert persistentState != null;
+            if (transientState.getModCount() != persistentState.getModCount()) {
+                throw new StaleItemStateException(transientState.getId() + " has been modified externally");
+            }
             sism.destroy(persistentState);
         }
     }
@@ -892,7 +895,11 @@ class ItemSaveOperation implements SessionWriteOperation<Object> {
                 // something went wrong, log exception and carry on
                 String msg = itemMgr.safeGetJCRPath(id)
                     + ": failed to restore transient state";
-                log.warn(msg, re);
+                if (log.isDebugEnabled()) {
+                    log.warn(msg, re);
+                } else {
+                    log.warn(msg);
+                }
             }
         }
     }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitRepositoryStub.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitRepositoryStub.java
index 5133a1f..32bf2f0 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitRepositoryStub.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/JackrabbitRepositoryStub.java
@@ -154,12 +154,8 @@ public class JackrabbitRepositoryStub extends RepositoryStub {
             }
 
             return getOrCreateRepository(dir, xml);
-
         } catch (Exception e) {
-            RepositoryStubException exception =
-                    new RepositoryStubException("Failed to start repository");
-            exception.initCause(e);
-            throw exception;
+            throw new RepositoryStubException("Failed to start repository", e);
         }
     }
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java
index e9f11d2..21b5843 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/LazyItemIterator.java
@@ -39,7 +39,7 @@ import org.slf4j.LoggerFactory;
 /**
  * <code>LazyItemIterator</code> is an id-based iterator that instantiates
  * the <code>Item</code>s only when they are requested.
- * <p/>
+ * <p>
  * <strong>Important:</strong> <code>Item</code>s that appear to be nonexistent
  * for some reason (e.g. because of insufficient access rights or because they
  * have been removed since the iterator has been retrieved) are silently
@@ -106,7 +106,7 @@ public class LazyItemIterator implements NodeIterator, PropertyIterator {
 
     /**
      * Prefetches next item.
-     * <p/>
+     * <p>
      * {@link #next} is set to the next available item in this iterator or to
      * <code>null</code> in case there are no more items.
      */
@@ -184,7 +184,7 @@ public class LazyItemIterator implements NodeIterator, PropertyIterator {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Note that the size of the iterator as reported by {@link #getSize()}
      * might appear to be shrinking while iterating because items that for
      * some reason cannot be retrieved through this iterator are silently
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java
index 63cf2c0..5046ce4 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/NodeImpl.java
@@ -92,6 +92,7 @@ import org.apache.jackrabbit.core.query.QueryManagerImpl;
 import org.apache.jackrabbit.core.security.AccessManager;
 import org.apache.jackrabbit.core.security.authorization.Permission;
 import org.apache.jackrabbit.core.session.AddNodeOperation;
+import org.apache.jackrabbit.core.session.NodeNameNormalizer;
 import org.apache.jackrabbit.core.session.SessionContext;
 import org.apache.jackrabbit.core.session.SessionOperation;
 import org.apache.jackrabbit.core.session.SessionWriteOperation;
@@ -189,7 +190,7 @@ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode {
     /**
      * Returns the id of the property at <code>relPath</code> or <code>null</code>
      * if no property exists at <code>relPath</code>.
-     * <p/>
+     * <p>
      * Note that access rights are not checked.
      *
      * @param relPath relative path of a (possible) property
@@ -207,7 +208,7 @@ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode {
     /**
      * Returns the id of the node at <code>relPath</code> or <code>null</code>
      * if no node exists at <code>relPath</code>.
-     * <p/>
+     * <p>
      * Note that access rights are not checked.
      *
      * @param relPath relative path of a (possible) node
@@ -245,7 +246,7 @@ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode {
     /**
      * Returns the id of the node at <code>p</code> or <code>null</code>
      * if no node exists at <code>p</code>.
-     * <p/>
+     * <p>
      * Note that access rights are not checked.
      *
      * @param p relative path of a (possible) node
@@ -280,7 +281,7 @@ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode {
     /**
      * Returns the id of the property at <code>p</code> or <code>null</code>
      * if no node exists at <code>p</code>.
-     * <p/>
+     * <p>
      * Note that access rights are not checked.
      *
      * @param p relative path of a (possible) node
@@ -425,17 +426,33 @@ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode {
             [...create block...]
 
         */
+        PropertyId propId = new PropertyId(getNodeId(), name);
         try {
-            PropertyId propId = new PropertyId(getNodeId(), name);
             return (PropertyImpl) itemMgr.getItem(propId);
         } catch (AccessDeniedException ade) {
             throw new ItemNotFoundException(name.toString());
         } catch (ItemNotFoundException e) {
-            // does not exist yet:
-            // find definition for the specified property and create property
+            // does not exist yet or has been removed transiently:
+            // find definition for the specified property and (re-)create property
             PropertyDefinitionImpl def = getApplicablePropertyDefinition(
                     name, type, multiValued, exactTypeMatch);
-            PropertyImpl prop = createChildProperty(name, type, def);
+            PropertyImpl prop;
+            if (stateMgr.hasTransientItemStateInAttic(propId)) {
+                // remove from attic
+                try {
+                    stateMgr.disposeTransientItemStateInAttic(stateMgr.getAttic().getItemState(propId));
+                } catch (ItemStateException ise) {
+                    // shouldn't happen because we checked if it is in the attic
+                    throw new RepositoryException(ise);
+                }
+                prop = (PropertyImpl) itemMgr.getItem(propId);
+                PropertyState state = (PropertyState) prop.getOrCreateTransientItemState();
+                state.setMultiValued(multiValued);
+                state.setType(type);
+                getNodeState().addPropertyName(name);
+            } else {
+                prop = createChildProperty(name, type, def);
+            }
             status.set(CREATED);
             return prop;
         }
@@ -595,8 +612,7 @@ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode {
     protected void removeChildNode(NodeId childId) throws RepositoryException {
         // modify the state of 'this', i.e. the parent node
         NodeState thisState = (NodeState) getOrCreateTransientItemState();
-        ChildNodeEntry entry =
-                thisState.getChildNodeEntry(childId);
+        ChildNodeEntry entry = thisState.getChildNodeEntry(childId);
         if (entry == null) {
             String msg = "failed to remove child " + childId + " of " + this;
             log.debug(msg);
@@ -923,9 +939,6 @@ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode {
      *         otherwise <code>false</code>
      */
     public boolean isNodeType(Name ntName) throws RepositoryException {
-        // check state of this instance
-        sanityCheck();
-
         // first do trivial checks without using type hierarchy
         Name primary = data.getNodeState().getNodeTypeName();
         if (ntName.equals(primary)) {
@@ -973,7 +986,7 @@ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode {
 
     /**
      * Sets the internal value of a property without checking any constraints.
-     * <p/>
+     * <p>
      * Note that no type conversion is being performed, i.e. it's the caller's
      * responsibility to make sure that the type of the given value is compatible
      * with the specified property's definition.
@@ -1013,7 +1026,7 @@ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode {
 
     /**
      * Sets the internal value of a property without checking any constraints.
-     * <p/>
+     * <p>
      * Note that no type conversion is being performed, i.e. it's the caller's
      * responsibility to make sure that the type of the given values is compatible
      * with the specified property's definition.
@@ -1038,7 +1051,7 @@ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode {
 
     /**
      * Sets the internal value of a property without checking any constraints.
-     * <p/>
+     * <p>
      * Note that no type conversion is being performed, i.e. it's the caller's
      * responsibility to make sure that the type of the given values is compatible
      * with the specified property's definition.
@@ -1271,6 +1284,9 @@ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode {
             nt = (NodeTypeImpl) def.getDefaultPrimaryType();
         }
 
+        // check the new name
+        NodeNameNormalizer.check(nodeName);
+
         // check for name collisions
         NodeState thisState = data.getNodeState();
         ChildNodeEntry cne = thisState.getChildNodeEntry(nodeName, 1);
@@ -1360,6 +1376,32 @@ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode {
     }
 
     /**
+     * Returns the name of the primary node type as exposed on the node state
+     * without retrieving the node type.
+     *
+     * @return the name of the primary node type.
+     */
+    public Name getPrimaryNodeTypeName() {
+        return data.getNodeState().getNodeTypeName();
+    }
+
+    /**
+     * Test if this node is access controlled. The node is access controlled if
+     * it is of node type
+     * {@link org.apache.jackrabbit.core.security.authorization.AccessControlConstants#NT_REP_ACCESS_CONTROLLABLE "rep:AccessControllable"}
+     * and if it has a child node named
+     * {@link org.apache.jackrabbit.core.security.authorization.AccessControlConstants#N_POLICY}.
+     *
+     * @return <code>true</code> if this node is access controlled and has a
+     * rep:policy child; <code>false</code> otherwise.
+     * @throws RepositoryException if an error occurs
+     */
+    public boolean isAccessControllable() throws RepositoryException {
+        return data.getNodeState().hasChildNodeEntry(NameConstants.REP_POLICY, 1)
+                    && isNodeType(NameConstants.REP_ACCESS_CONTROLLABLE);
+    }
+
+    /**
      * Same as <code>{@link Node#orderBefore(String, String)}</code> except that
      * this method takes a <code>Path.Element</code> arguments instead of
      * <code>String</code>s.
@@ -2020,6 +2062,18 @@ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode {
                     removeChildProperty(name);
                 }
                 throw e; // rethrow
+            } catch (RuntimeException e) {
+                if (status.get(CREATED)) {
+                    // setting value failed, get rid of newly created property
+                    removeChildProperty(name);
+                }
+                throw e; // rethrow
+            } catch (Error e) {
+                if (status.get(CREATED)) {
+                    // setting value failed, get rid of newly created property
+                    removeChildProperty(name);
+                }
+                throw e; // rethrow
             }
             return property;
         }
@@ -2268,6 +2322,9 @@ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode {
      * {@inheritDoc}
      */
     public boolean isNodeType(String nodeTypeName) throws RepositoryException {
+        // check state of this instance
+        sanityCheck();
+
         try {
             return isNodeType(sessionContext.getQName(nodeTypeName));
         } catch (NameException e) {
@@ -2604,11 +2661,11 @@ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode {
     /**
      * A special kind of <code>remove()</code> that removes this node and every
      * other node in the shared set of this node.
-     * <p/>
+     * <p>
      * This removal must be done atomically, i.e., if one of the nodes cannot be
      * removed, the function throws the exception <code>remove()</code> would
      * have thrown in that case, and none of the nodes are removed.
-     * <p/>
+     * <p>
      * If this node is not shared this method removes only this node.
      *
      * @throws VersionException
@@ -2634,12 +2691,12 @@ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode {
     /**
      * A special kind of <code>remove()</code> that removes this node, but does
      * not remove any other node in the shared set of this node.
-     * <p/>
+     * <p>
      * All of the exceptions defined for <code>remove()</code> apply to this
      * function. In addition, a <code>RepositoryException</code> is thrown if
      * this node cannot be removed without removing another node in the shared
      * set of this node.
-     * <p/>
+     * <p>
      * If this node is not shared this method removes only this node.
      *
      * @throws VersionException
@@ -2810,7 +2867,13 @@ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode {
             PropertyId id = new PropertyId(state.getNodeId(), JCR_ISCHECKEDOUT);
             PropertyState ps =
                 (PropertyState) sessionContext.getItemStateManager().getItemState(id);
-            return ps.getValues()[0].getBoolean();
+            InternalValue[] values = ps.getValues();
+            if (values == null || values.length != 1) {
+                // the property is not fully set, or it is a multi-valued property
+                // in which case it's probably not mix:versionable
+                return true;
+            }
+            return values[0].getBoolean();
         } catch (ItemStateException e) {
             throw new RepositoryException(e);
         }
@@ -2953,7 +3016,8 @@ public class NodeImpl extends ItemImpl implements Node, JackrabbitNode {
         // check state of this instance
         sanityCheck();
         LockManager lockMgr = getSession().getWorkspace().getLockManager();
-        return lockMgr.lock(getPath(), isDeep, isSessionScoped, Long.MAX_VALUE, null);
+        return lockMgr.lock(getPath(), isDeep, isSessionScoped,
+                sessionContext.getWorkspace().getConfig().getDefaultLockTimeout(), null);
     }
 
     /**
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java
index f22659e..2da032f 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ProtectedItemModifier.java
@@ -16,10 +16,17 @@
  */
 package org.apache.jackrabbit.core;
 
+import javax.jcr.AccessDeniedException;
+import javax.jcr.ItemExistsException;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.nodetype.NodeTypeImpl;
 import org.apache.jackrabbit.core.retention.RetentionManagerImpl;
 import org.apache.jackrabbit.core.security.AccessManager;
+import org.apache.jackrabbit.core.security.authentication.token.TokenProvider;
 import org.apache.jackrabbit.core.security.authorization.Permission;
 import org.apache.jackrabbit.core.security.authorization.acl.ACLEditor;
 import org.apache.jackrabbit.core.security.user.UserManagerImpl;
@@ -30,12 +37,6 @@ import org.apache.jackrabbit.core.value.InternalValue;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.Path;
 
-import javax.jcr.AccessDeniedException;
-import javax.jcr.ItemExistsException;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-
 /**
  * <code>ProtectedItemModifier</code>: An abstract helper class to allow classes
  * residing outside of the core package to modify and remove protected items.
@@ -58,6 +59,7 @@ public abstract class ProtectedItemModifier {
         if (!(UserManagerImpl.class.isAssignableFrom(cl) ||
               RetentionManagerImpl.class.isAssignableFrom(cl) ||
               ACLEditor.class.isAssignableFrom(cl) ||
+              TokenProvider.class.isAssignableFrom(cl) ||
               org.apache.jackrabbit.core.security.authorization.principalbased.ACLEditor.class.isAssignableFrom(cl))) {
             throw new IllegalArgumentException("Only UserManagerImpl, RetentionManagerImpl and ACLEditor may extend from the ProtectedItemModifier");
         }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java
index 3113a9e..eff7964 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryChecker.java
@@ -23,6 +23,7 @@ import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_PREDECESS
 import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_ROOTVERSION;
 import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_VERSIONHISTORY;
 import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_VERSIONABLE;
+import static org.apache.jackrabbit.spi.commons.name.NameConstants.MIX_REFERENCEABLE;
 
 import java.util.Calendar;
 import java.util.HashSet;
@@ -72,8 +73,20 @@ class RepositoryChecker {
 
     private final InternalVersionManagerImpl versionManager;
 
-    public RepositoryChecker(
-            PersistenceManager workspace,
+    // maximum size of changelog when running in "fixImmediately" mode
+    private final static long CHUNKSIZE = 256;
+
+    // number of nodes affected by pending changes
+    private long dirtyNodes = 0;
+
+    // total nodes checked, with problems
+    private long totalNodes = 0;
+    private long brokenNodes = 0;
+
+    // start time
+    private long startTime;
+
+    public RepositoryChecker(PersistenceManager workspace,
             InternalVersionManagerImpl versionManager) {
         this.workspace = workspace;
         this.workspaceChanges = new ChangeLog();
@@ -81,17 +94,41 @@ class RepositoryChecker {
         this.versionManager = versionManager;
     }
 
-    public void check(NodeId id, boolean recurse)
+    public void check(NodeId id, boolean recurse, boolean fixImmediately)
             throws RepositoryException {
+
+        log.info("Starting RepositoryChecker");
+
+        startTime = System.currentTimeMillis();
+
+        internalCheck(id, recurse, fixImmediately);
+
+        if (fixImmediately) {
+            internalFix(true);
+        }
+
+        log.info("RepositoryChecker finished; checked " + totalNodes
+                + " nodes in " + (System.currentTimeMillis() - startTime)
+                + "ms, problems found: " + brokenNodes);
+    }
+
+    private void internalCheck(NodeId id, boolean recurse,
+            boolean fixImmediately) throws RepositoryException {
         try {
             log.debug("Checking consistency of node {}", id);
+            totalNodes += 1;
+
             NodeState state = workspace.load(id);
             checkVersionHistory(state);
 
+            if (fixImmediately && dirtyNodes > CHUNKSIZE) {
+                internalFix(false);
+            }
+
             if (recurse) {
                 for (ChildNodeEntry child : state.getChildNodeEntries()) {
                     if (!SYSTEM_ROOT_NODE_ID.equals(child.getId())) {
-                        check(child.getId(), recurse);
+                        internalCheck(child.getId(), recurse, fixImmediately);
                     }
                 }
             }
@@ -100,26 +137,38 @@ class RepositoryChecker {
         }
     }
 
-    private void fix(PersistenceManager pm, ChangeLog changes, String store)
-            throws RepositoryException {
+    private void fix(PersistenceManager pm, ChangeLog changes, String store,
+            boolean verbose) throws RepositoryException {
         if (changes.hasUpdates()) {
-            log.warn("Fixing " + store + " inconsistencies");
+            if (log.isWarnEnabled()) {
+                log.warn("Fixing " + store + " inconsistencies: "
+                        + changes.toString());
+            }
             try {
                 pm.store(changes);
+                changes.reset();
             } catch (ItemStateException e) {
-                String message = "Failed to fix " + store + " inconsistencies (aborting)";
+                String message = "Failed to fix " + store
+                        + " inconsistencies (aborting)";
                 log.error(message, e);
                 throw new RepositoryException(message, e);
             }
         } else {
-            log.info("No " + store + "  inconsistencies found");
+            if (verbose) {
+                log.info("No " + store + " inconsistencies found");
+            }
         }
     }
 
     public void fix() throws RepositoryException {
-        fix(workspace, workspaceChanges, "workspace");
+        internalFix(true);
+    }
+
+    private void internalFix(boolean verbose) throws RepositoryException {
+        fix(workspace, workspaceChanges, "workspace", verbose);
         fix(versionManager.getPersistenceManager(), vworkspaceChanges,
-                "versioning workspace");
+                "versioning workspace", verbose);
+        dirtyNodes = 0;
     }
 
     private void checkVersionHistory(NodeState node) {
@@ -132,7 +181,7 @@ class RepositoryChecker {
 
         try {
             String type = isVersioned ? "in-use" : "candidate";
-            
+
             log.debug("Checking " + type + " version history of node {}", nid);
 
             String intro = "Removing references to an inconsistent " + type
@@ -149,7 +198,7 @@ class RepositoryChecker {
             message = intro + " (getting the InternalVersionHistory)";
 
             InternalVersionHistory vh = null;
-            
+
             try {
                 vh = versionManager.getVersionHistoryOfNode(nid);
             }
@@ -167,7 +216,7 @@ class RepositoryChecker {
                 }
             } else { 
                 vhid = vh.getId();
-                
+
                 // additional checks, see JCR-3101
 
                 message = intro + " (getting the version names failed)";
@@ -212,11 +261,17 @@ class RepositoryChecker {
 
     // un-versions the node, and potentially moves the version history away
     private void removeVersionHistoryReferences(NodeState node,  NodeId vhid) {
+
+        dirtyNodes += 1;
+        brokenNodes += 1;
+
         NodeState modified =
             new NodeState(node, NodeState.STATUS_EXISTING_MODIFIED, true);
 
         Set<Name> mixins = new HashSet<Name>(node.getMixinTypeNames());
         if (mixins.remove(MIX_VERSIONABLE)) {
+            // we are keeping jcr:uuid, so we need to make sure the type info stays valid
+            mixins.add(MIX_REFERENCEABLE);
             modified.setMixinTypeNames(mixins);
         }
 
@@ -226,29 +281,29 @@ class RepositoryChecker {
         removeProperty(modified, JCR_ISCHECKEDOUT);
 
         workspaceChanges.modified(modified);
-        
+
         if (vhid != null) {
             // attempt to rename the version history, so it doesn't interfere with
             // a future attempt to put the node under version control again 
             // (see JCR-3115)
-            
+
             log.info("trying to rename version history of node " + node.getId());
 
             NameFactory nf = NameFactoryImpl.getInstance();
-            
+
             // Name of VHR in parent folder is ID of versionable node
             Name vhrname = nf.create(Name.NS_DEFAULT_URI, node.getId().toString());
 
             try {
                 NodeState vhrState = versionManager.getPersistenceManager().load(vhid);
                 NodeState vhrParentState = versionManager.getPersistenceManager().load(vhrState.getParentId());
-                
+
                 if (vhrParentState.hasChildNodeEntry(vhrname)) {
                     NodeState modifiedParent = (NodeState) vworkspaceChanges.get(vhrState.getParentId());
                     if (modifiedParent == null) {
                         modifiedParent = new NodeState(vhrParentState, NodeState.STATUS_EXISTING_MODIFIED, true);
                     }
-                    
+
                     Calendar now = Calendar.getInstance();
                     String appendme = " (disconnected by RepositoryChecker on "
                             + ISO8601.format(now) + ")";
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java
index 6b41b83..e98cc8d 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryContext.java
@@ -20,8 +20,10 @@ import java.io.File;
 import java.io.IOException;
 import java.util.concurrent.ScheduledExecutorService;
 
+import javax.jcr.NoSuchWorkspaceException;
 import javax.jcr.RepositoryException;
 
+import org.apache.jackrabbit.core.RepositoryImpl.WorkspaceInfo;
 import org.apache.jackrabbit.core.cluster.ClusterNode;
 import org.apache.jackrabbit.core.config.RepositoryConfig;
 import org.apache.jackrabbit.core.data.DataStore;
@@ -32,7 +34,7 @@ import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
 import org.apache.jackrabbit.core.security.JackrabbitSecurityManager;
 import org.apache.jackrabbit.core.security.authorization.PrivilegeRegistry;
 import org.apache.jackrabbit.core.state.ItemStateCacheFactory;
-import org.apache.jackrabbit.core.stats.RepositoryStatisticsImpl;
+import org.apache.jackrabbit.stats.RepositoryStatisticsImpl;
 import org.apache.jackrabbit.core.stats.StatManager;
 import org.apache.jackrabbit.core.version.InternalVersionManagerImpl;
 
@@ -122,6 +124,12 @@ public class RepositoryContext {
      * The Statistics manager, handles statistics
      */
     private StatManager statManager;
+    
+    /**
+     *  flag to indicate if GC is running
+     */
+    
+    private boolean gcRunning;
 
     /**
      * Creates a component context for the given repository.
@@ -168,6 +176,10 @@ public class RepositoryContext {
         return create(RepositoryConfig.install(dir));
     }
 
+    public RepositoryConfig getRepositoryConfig() {
+        return repository.getConfig();
+    }
+
     /**
      * Returns the repository instance to which this context is associated.
      *
@@ -366,6 +378,21 @@ public class RepositoryContext {
     }
 
     /**
+     * Returns the {@link WorkspaceInfo} for the named workspace.
+     *
+     * @param workspaceName The name of the workspace whose {@link WorkspaceInfo}
+     *                      is to be returned. This must not be <code>null</code>.
+     * @return The {@link WorkspaceInfo} for the named workspace. This will
+     *         never be <code>null</code>.
+     * @throws NoSuchWorkspaceException If the named workspace does not exist.
+     * @throws RepositoryException If this repository has been shut down.
+     */
+    public WorkspaceInfo getWorkspaceInfo(String workspaceName)
+            throws NoSuchWorkspaceException, RepositoryException {
+        return repository.getWorkspaceInfo(workspaceName);
+    }
+
+    /**
      * Returns the security manager of this repository.
      *
      * @return security manager
@@ -429,4 +456,22 @@ public class RepositoryContext {
         return statManager;
     }
 
+    /**
+     * 
+     * @return gcRunning status
+     */
+    public boolean isGcRunning() {
+        return gcRunning;
+    }
+
+    /**
+     * set gcRunnign status
+     * @param gcRunning
+     */
+    public synchronized void setGcRunning(boolean gcRunning) {
+        this.gcRunning = gcRunning;
+    }
+    
+    
+
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java
index 32ad385..06645e3 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java
@@ -72,10 +72,10 @@ import org.apache.jackrabbit.core.config.VersioningConfig;
 import org.apache.jackrabbit.core.config.WorkspaceConfig;
 import org.apache.jackrabbit.core.data.DataStore;
 import org.apache.jackrabbit.core.data.DataStoreException;
-import org.apache.jackrabbit.core.data.GarbageCollector;
 import org.apache.jackrabbit.core.fs.FileSystem;
 import org.apache.jackrabbit.core.fs.FileSystemException;
 import org.apache.jackrabbit.core.fs.FileSystemResource;
+import org.apache.jackrabbit.core.gc.GarbageCollector;
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.id.NodeIdFactory;
 import org.apache.jackrabbit.core.lock.LockManager;
@@ -89,6 +89,7 @@ import org.apache.jackrabbit.core.observation.ObservationDispatcher;
 import org.apache.jackrabbit.core.persistence.IterablePersistenceManager;
 import org.apache.jackrabbit.core.persistence.PMContext;
 import org.apache.jackrabbit.core.persistence.PersistenceManager;
+import org.apache.jackrabbit.core.persistence.check.ConsistencyChecker;
 import org.apache.jackrabbit.core.retention.RetentionRegistry;
 import org.apache.jackrabbit.core.retention.RetentionRegistryImpl;
 import org.apache.jackrabbit.core.security.JackrabbitSecurityManager;
@@ -105,6 +106,7 @@ import org.apache.jackrabbit.core.util.RepositoryLockMechanism;
 import org.apache.jackrabbit.core.version.InternalVersionManager;
 import org.apache.jackrabbit.core.version.InternalVersionManagerImpl;
 import org.apache.jackrabbit.core.xml.ClonedInputSource;
+import org.apache.jackrabbit.data.core.TransactionException;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
 import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
 import org.apache.jackrabbit.spi.commons.namespace.RegistryNamespaceResolver;
@@ -162,6 +164,12 @@ public class RepositoryImpl extends AbstractRepository
     private static final String PROPERTIES_RESOURCE = "repository.properties";
 
     /**
+     * Key to a <code>string</code> descriptor. Returns the repository cluster id if
+     * and only if clustering is enabled.
+     */
+    public static final String JACKRABBIT_CLUSTER_ID = "jackrabbit.cluster.id";
+
+    /**
      * the repository descriptors, maps String keys to Value/Value[] objects
      */
     private final Map<String, DescriptorValue> repDescriptors = new HashMap<String, DescriptorValue>();
@@ -331,6 +339,7 @@ public class RepositoryImpl extends AbstractRepository
 
             // now start cluster node as last step
             if (clusterNode != null) {
+                setDescriptor(JACKRABBIT_CLUSTER_ID, repConfig.getClusterConfig().getId());
                 try {
                     clusterNode.start();
                 } catch (ClusterException e) {
@@ -460,7 +469,31 @@ public class RepositoryImpl extends AbstractRepository
         // manager has been added to the repository context, since the
         // initialisation code may invoke code that depends on the presence
         // of a security manager. It would be better if this was not the case.
-        securityMgr.init(this, getSystemSession(workspaceName));
+        SystemSession systemSession = getSystemSession(workspaceName);
+        securityMgr.init(this, systemSession);
+
+        // initial security specific repository descriptors that are defined
+        // by JackrabbitRepository
+        ValueFactory vf = ValueFactoryImpl.getInstance();
+        boolean hasUserMgt;
+        try {
+            securityMgr.getUserManager(systemSession);
+            hasUserMgt = true;
+        } catch (RepositoryException e) {
+            hasUserMgt = false;
+        }
+        setDescriptor(JackrabbitRepository.OPTION_USER_MANAGEMENT_SUPPORTED, vf.createValue(hasUserMgt));
+
+        boolean hasPrincipalMgt;
+        try {
+            securityMgr.getPrincipalManager(systemSession);
+            hasPrincipalMgt = true;
+        } catch (RepositoryException e) {
+            hasPrincipalMgt = false;
+        }
+        setDescriptor(JackrabbitRepository.OPTION_PRINCIPAL_MANAGEMENT_SUPPORTED, vf.createValue(hasPrincipalMgt));
+        setDescriptor(JackrabbitRepository.OPTION_PRIVILEGE_MANAGEMENT_SUPPORTED, vf.createValue(true));
+
     }
 
     /**
@@ -571,7 +604,7 @@ public class RepositoryImpl extends AbstractRepository
 
     /**
      * Creates a new <code>RepositoryImpl</code> instance.
-     * <p/>
+     * <p>
      *
      * @param config the configuration of the repository
      * @return a new <code>RepositoryImpl</code> instance
@@ -600,7 +633,7 @@ public class RepositoryImpl extends AbstractRepository
      * Returns the system search manager or <code>null</code> if none is
      * configured.
      */
-    private SearchManager getSystemSearchManager(String wspName)
+    protected SearchManager getSystemSearchManager(String wspName)
             throws RepositoryException {
         if (systemSearchMgr == null) {
             if (repConfig.isSearchEnabled()) {
@@ -902,7 +935,7 @@ public class RepositoryImpl extends AbstractRepository
      * Creates a new repository session on the specified workspace for the
      * <b><i>authenticated</i></b> subject of the given login context and
      * adds it to the <i>active</i> sessions.
-     * <p/>
+     * <p>
      * Calls {@link #createSessionInstance(AuthContext, WorkspaceConfig)} to
      * create the actual <code>SessionImpl</code> instance.
      *
@@ -931,7 +964,7 @@ public class RepositoryImpl extends AbstractRepository
      * Creates a new repository session on the specified workspace for the given
      * <b><i>authenticated</i></b> subject and adds it to the <i>active</i>
      * sessions.
-     * <p/>
+     * <p>
      * Calls {@link #createSessionInstance(Subject, WorkspaceConfig)} to
      * create the actual <code>SessionImpl</code> instance.
      *
@@ -993,7 +1026,7 @@ public class RepositoryImpl extends AbstractRepository
             AccessControlContext acc = AccessController.getContext();
             subject = Subject.getSubject(acc);
         } catch (SecurityException e) {
-            log.warn("Can't check for preauthentication. Reason:", e.getMessage());
+            log.warn("Can't check for preauthentication. Reason: {}", e.getMessage());
         }
         if (subject == null) {
             log.debug("No preauthenticated subject found -> return null.");
@@ -1171,7 +1204,7 @@ public class RepositoryImpl extends AbstractRepository
      * <ul>
      * <li>Sets standard descriptors</li>
      * <li>{@link #getCustomRepositoryDescriptors()} is called
-     * afterwards in order to add custom/overwrite standard repository decriptors.</li>
+     * afterwards in order to add custom/overwrite standard repository descriptors.</li>
      * </ul>
      *
      * @throws RepositoryException
@@ -1271,13 +1304,13 @@ public class RepositoryImpl extends AbstractRepository
     /**
      * Returns a <code>Properties</code> object containing custom repository
      * descriptors or <code>null</code> if none exist.
-     * <p/>
+     * <p>
      * Overridable to allow subclasses to add custom descriptors or to
      * override standard descriptor values.
-     * <p/>
+     * <p>
      * Note that the properties entries will be set as single-valued <code>STRING</code>
      * descriptor values.
-     * <p/>
+     * <p>
      * This method tries to load the <code>Properties</code> from the
      * <code>org/apache/jackrabbit/core/repository.properties</code> resource
      * found in the class path.
@@ -1384,7 +1417,7 @@ public class RepositoryImpl extends AbstractRepository
         PersistenceManager pm = vm.getPersistenceManager();
         pmList.add(pm);
         String[] wspNames = getWorkspaceNames();
-        Session[] sessions = new Session[wspNames.length];
+        SessionImpl[] sessions = new SessionImpl[wspNames.length];
         for (int i = 0; i < wspNames.length; i++) {
             String wspName = wspNames[i];
             WorkspaceInfo wspInfo = getWorkspaceInfo(wspName);
@@ -1415,7 +1448,14 @@ public class RepositoryImpl extends AbstractRepository
             }
             ipmList[i] = (IterablePersistenceManager) pm;
         }
-        return new GarbageCollector(context.getDataStore(), ipmList, sessions);
+        GarbageCollector gc = new GarbageCollector(context, context.getDataStore(), ipmList, sessions);
+        synchronized (this) {
+            if (context.isGcRunning()) {
+                throw new RepositoryException("Cannot create GC. GC already running");
+            }
+            context.setGcRunning(true);
+        }
+        return gc;
     }
 
     //-----------------------------------------------------------< Repository >
@@ -1608,7 +1648,7 @@ public class RepositoryImpl extends AbstractRepository
      * representing the same named workspace, i.e. the same physical
      * storage.
      */
-    protected class WorkspaceInfo implements UpdateEventListener {
+    public class WorkspaceInfo implements UpdateEventListener {
 
         /**
          * workspace configuration (passed in constructor)
@@ -1794,7 +1834,7 @@ public class RepositoryImpl extends AbstractRepository
          * @throws RepositoryException if the persistence manager could not be
          * instantiated/initialized
          */
-        protected PersistenceManager getPersistenceManager()
+        public PersistenceManager getPersistenceManager()
                 throws RepositoryException {
             if (!isInitialized()) {
                 throw new IllegalStateException("workspace '" + getName()
@@ -2034,6 +2074,9 @@ public class RepositoryImpl extends AbstractRepository
                     updateChannel = clusterNode.createUpdateChannel(getName());
                     itemStateMgr.setEventChannel(updateChannel);
                     updateChannel.setListener(this);
+                    if (persistMgr instanceof ConsistencyChecker) {
+                        ((ConsistencyChecker) persistMgr).setEventChannel(updateChannel);
+                    }
                 }
             } catch (ItemStateException ise) {
                 String msg = "failed to instantiate shared item state manager";
@@ -2055,8 +2098,7 @@ public class RepositoryImpl extends AbstractRepository
             if (Boolean.getBoolean("org.apache.jackrabbit.version.recovery")) {
                 RepositoryChecker checker = new RepositoryChecker(
                         persistMgr, context.getInternalVersionManager());
-                checker.check(ROOT_NODE_ID, true);
-                checker.fix();
+                checker.check(ROOT_NODE_ID, true, true);
             }
         }
 
@@ -2317,7 +2359,7 @@ public class RepositoryImpl extends AbstractRepository
 
         /**
          * {@inheritDoc}
-         * <p/>
+         * <p>
          * Performs the following tasks in a <code>while (true)</code> loop:
          * <ol>
          * <li>wait for <code>checkInterval</code> milliseconds</li>
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java
index d62a56e..dc4fcb0 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionImpl.java
@@ -70,7 +70,7 @@ import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.api.security.user.UserManager;
 import org.apache.jackrabbit.commons.AbstractSession;
 import org.apache.jackrabbit.core.config.WorkspaceConfig;
-import org.apache.jackrabbit.core.data.GarbageCollector;
+import org.apache.jackrabbit.core.gc.GarbageCollector;
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl;
 import org.apache.jackrabbit.core.observation.ObservationManagerImpl;
@@ -952,6 +952,8 @@ public class SessionImpl extends AbstractSession
             // notify listeners that session is about to be closed
             notifyLoggingOut();
 
+            context.getPrivilegeManager().dispose();
+            context.getNodeTypeManager().dispose();
             // dispose session item state manager
             context.getItemStateManager().dispose();
             // dispose item manager
@@ -1095,7 +1097,7 @@ public class SessionImpl extends AbstractSession
         try {
             nodeId = NodeId.valueOf(id);
         } catch (IllegalArgumentException iae) {
-            throw new RepositoryException("invalid identifier: " + id);
+            throw new RepositoryException("invalid identifier: " + id,iae);
         }
         return getNodeById(nodeId);
     }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionMoveOperation.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionMoveOperation.java
index 47f6775..314ca02 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionMoveOperation.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SessionMoveOperation.java
@@ -128,12 +128,16 @@ public class SessionMoveOperation implements SessionWriteOperation<Object> {
             // no name collision, fall through
         }
 
+        // verify that the targetNode can be removed
+        int options = ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION;
+        context.getItemValidator().checkRemove(targetNode, options, Permission.NONE);
+
         // verify for both source and destination parent nodes that
         // - they are checked-out
         // - are not protected neither by node type constraints nor by retention/hold
-        int options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK |
+        options = ItemValidator.CHECK_CHECKED_OUT | ItemValidator.CHECK_LOCK |
         ItemValidator.CHECK_CONSTRAINTS | ItemValidator.CHECK_HOLD | ItemValidator.CHECK_RETENTION;
-        context.getItemValidator().checkRemove(srcParentNode, options, Permission.NONE);
+        context.getItemValidator().checkModify(srcParentNode, options, Permission.NONE);
         context.getItemValidator().checkModify(destParentNode, options, Permission.NONE);
 
         // check constraints
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SystemSession.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SystemSession.java
index b5fc870..b181d81 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SystemSession.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/SystemSession.java
@@ -89,7 +89,7 @@ class SystemSession extends SessionImpl {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Overridden in order to create custom access manager
      *
      * @return access manager for system session
@@ -122,27 +122,6 @@ class SystemSession extends SessionImpl {
         return false;
     }
 
-    /**
-     * Creates and returns a new <em>system</em> session for the
-     * given workspace.
-     *
-     * @param workspaceName workspace name,
-     *                      or <code>null</code> for the default workspace
-     */
-    @Override
-    public SessionImpl createSession(String workspaceName)
-            throws RepositoryException {
-        if (workspaceName == null) {
-            WorkspaceManager wm = repositoryContext.getWorkspaceManager();
-            workspaceName = wm.getDefaultWorkspaceName();
-        }
-
-        RepositoryImpl repository = repositoryContext.getRepository();
-        WorkspaceConfig wspConfig =
-            repository.getWorkspaceInfo(workspaceName).getConfig();
-        return create(repositoryContext, wspConfig);
-    }
-
     //--------------------------------------------------------< inner classes >
     /**
      * An access manager that grants access to everything.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/TransactionContext.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/TransactionContext.java
deleted file mode 100644
index 41fc66b..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/TransactionContext.java
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core;
-
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.transaction.xa.XAException;
-import javax.transaction.xa.Xid;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Represents the transaction on behalf of the component that wants to
- * explicitly demarcate transaction boundaries. After having been prepared,
- * schedules a task that rolls back the transaction if some time passes without
- * any further action. This will guarantee that global objects locked by one
- * of the resources' {@link InternalXAResource#prepare} method, are eventually
- * unlocked.
- */
-public class TransactionContext {
-
-    /**
-     * Logger instance.
-     */
-    private static final Logger log = LoggerFactory.getLogger(TransactionContext.class);
-
-    private static final int STATUS_PREPARING = 1;
-    private static final int STATUS_PREPARED = 2;
-    private static final int STATUS_COMMITTING = 3;
-    private static final int STATUS_COMMITTED = 4;
-    private static final int STATUS_ROLLING_BACK = 5;
-    private static final int STATUS_ROLLED_BACK = 6;
-
-    /**
-     * The per thread associated Xid
-     */
-    private static final ThreadLocal<Xid> CURRENT_XID = new ThreadLocal<Xid>();
-
-    /**
-     * Transactional resources.
-     */
-    private final InternalXAResource[] resources;
-
-    /**
-    * The Xid
-    */
-   private final Xid xid;
-
-    /**
-     * Transaction attributes.
-     */
-    private final Map<String, Object> attributes = new HashMap<String, Object>();
-
-    /**
-     * Status.
-     */
-    private int status;
-
-    /**
-     * Flag indicating whether the association is currently suspended.
-     */
-    private boolean suspended;
-
-    /**
-     * Create a new instance of this class.
-     *
-     * @param xid associated xid
-     * @param resources transactional resources
-     */
-    public TransactionContext(Xid xid, InternalXAResource[] resources) {
-        this.xid = xid;
-        this.resources = resources;
-    }
-
-    /**
-     * Set an attribute on this transaction. If the value specified is
-     * <code>null</code>, it is semantically equivalent to
-     * {@link #removeAttribute}.
-     *
-     * @param name  attribute name
-     * @param value attribute value
-     */
-    public void setAttribute(String name, Object value) {
-        if (value == null) {
-            removeAttribute(name);
-        }
-        attributes.put(name, value);
-    }
-
-    /**
-     * Return an attribute value on this transaction.
-     *
-     * @param name attribute name
-     * @return attribute value, <code>null</code> if no attribute with that
-     *         name exists
-     */
-    public Object getAttribute(String name) {
-        return attributes.get(name);
-    }
-
-    /**
-     * Remove an attribute on this transaction.
-     *
-     * @param name attribute name
-     */
-    public void removeAttribute(String name) {
-        attributes.remove(name);
-    }
-
-    /**
-     * Prepare the transaction identified by this context. Prepares changes on
-     * all resources. If some resource reports an error on prepare,
-     * automatically rollback changes on all other resources. Throw exception
-     * at the end if errors were found.
-     *
-     * @throws XAException if an error occurs
-     */
-    public synchronized void prepare() throws XAException {
-        bindCurrentXid();
-        status = STATUS_PREPARING;
-        beforeOperation();
-
-        TransactionException txe = null;
-        for (int i = 0; i < resources.length; i++) {
-            try {
-                resources[i].prepare(this);
-            } catch (TransactionException e) {
-                txe = e;
-                break;
-            }
-        }
-
-        afterOperation();
-        status = STATUS_PREPARED;
-
-        if (txe != null) {
-            // force immediate rollback on error.
-            try {
-                rollback();
-            } catch (XAException e) {
-                /* ignore */
-            }
-            XAException e = new XAException(XAException.XA_RBOTHER);
-            e.initCause(txe);
-            throw e;
-        }
-    }
-
-    /**
-     * Commit the transaction identified by this context. Commits changes on
-     * all resources. If some resource reports an error on commit,
-     * automatically rollback changes on all other resources. Throw
-     * exception at the end if some commit failed.
-     *
-     * @throws XAException if an error occurs
-     */
-    public synchronized void commit() throws XAException {
-        if (status == STATUS_ROLLED_BACK) {
-            throw new XAException(XAException.XA_HEURRB);
-        }
-
-        boolean heuristicCommit = false;
-        bindCurrentXid();
-        status = STATUS_COMMITTING;
-        beforeOperation();
-
-        TransactionException txe = null;
-        for (int i = 0; i < resources.length; i++) {
-            InternalXAResource resource = resources[i];
-            if (txe != null) {
-                try {
-                    resource.rollback(this);
-                } catch (TransactionException e) {
-                    log.warn("Unable to rollback changes on " + resource, e);
-                }
-            } else {
-                try {
-                    resource.commit(this);
-                    heuristicCommit = true;
-                } catch (TransactionException e) {
-                    txe = e;
-                }
-            }
-        }
-        afterOperation();
-        status = STATUS_COMMITTED;
-
-        cleanCurrentXid();
-
-        if (txe != null) {
-            XAException e = null;
-            if (heuristicCommit) {
-                e = new XAException(XAException.XA_HEURMIX);
-            } else {
-                e = new XAException(XAException.XA_HEURRB);
-            }
-            e.initCause(txe);
-            throw e;
-        }
-    }
-
-    /**
-     * Rollback the transaction identified by this context. Rolls back changes
-     * on all resources. Throws exception at the end if errors were found.
-     * @throws XAException if an error occurs
-     */
-    public synchronized void rollback() throws XAException {
-        if (status == STATUS_ROLLED_BACK) {
-            throw new XAException(XAException.XA_RBOTHER);
-        }
-        bindCurrentXid();
-        status = STATUS_ROLLING_BACK;
-        beforeOperation();
-
-        int errors = 0;
-        for (int i = 0; i < resources.length; i++) {
-            InternalXAResource resource = resources[i];
-            try {
-                resource.rollback(this);
-            } catch (TransactionException e) {
-                log.warn("Unable to rollback changes on " + resource, e);
-                errors++;
-            }
-        }
-        afterOperation();
-        status = STATUS_ROLLED_BACK;
-
-        cleanCurrentXid();
-
-        if (errors != 0) {
-            throw new XAException(XAException.XA_RBOTHER);
-        }
-    }
-
-    /**
-     * Invoke all of the registered resources' {@link InternalXAResource#beforeOperation}
-     * methods.
-     */
-    private void beforeOperation() {
-        for (int i = 0; i < resources.length; i++) {
-            resources[i].beforeOperation(this);
-        }
-    }
-
-    /**
-     * Invoke all of the registered resources' {@link InternalXAResource#afterOperation}
-     * methods.
-     */
-    private void afterOperation() {
-        for (int i = 0; i < resources.length; i++) {
-            resources[i].afterOperation(this);
-        }
-    }
-
-    /**
-     * Return a flag indicating whether the association is suspended.
-     *
-     * @return <code>true</code> if the association is suspended;
-     *         <code>false</code> otherwise
-     */
-    public boolean isSuspended() {
-        return suspended;
-    }
-
-    /**
-     * Set a flag indicating whether the association is suspended.
-     *
-     * @param suspended flag whether that the association is suspended.
-     */
-    public void setSuspended(boolean suspended) {
-        this.suspended = suspended;
-    }
-
-    /**
-     * Helper Method to bind the {@link Xid} associated with this {@link TransactionContext}
-     * to the {@link #CURRENT_XID} ThreadLocal.
-     */
-    private void bindCurrentXid() {
-        CURRENT_XID.set(xid);
-    }
-
-    /**
-     * Helper Method to clean the {@link Xid} associated with this {@link TransactionContext}
-     * from the {@link #CURRENT_XID} ThreadLocal.
-     */
-    private void cleanCurrentXid() {
-        CURRENT_XID.set(null);
-    }
-
-    /**
-     * Returns the {@link Xid} bind to the {@link #CURRENT_XID} ThreadLocal
-     * @return current Xid or null
-     */
-    public static Xid getCurrentXid() {
-        return CURRENT_XID.get();
-    }
-
-    /**
-     * Returns the current thread identifier. The identifier is either the
-     * current thread instance or the global transaction identifier when
-     * running under a transaction.
-     *
-     * @return current thread identifier
-     */
-    public static Object getCurrentThreadId() {
-        Xid xid = TransactionContext.getCurrentXid();
-        if (xid != null) {
-            return xid.getGlobalTransactionId();
-        } else {
-            return Thread.currentThread();
-        }
-    }
-
-    /**
-     * Compares the given thread identifiers for equality.
-     *
-     * @see #getCurrentThreadId()
-     */
-    public static boolean isSameThreadId(Object a, Object b) {
-        if (a == b) {
-            return true;
-        } else if (a instanceof byte[] && b instanceof byte[]) {
-            return Arrays.equals((byte[]) a, (byte[]) b);
-        } else {
-            return false;
-        }
-    }
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/TransactionException.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/TransactionException.java
deleted file mode 100644
index 692d021..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/TransactionException.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core;
-
-/**
- * TransactionException is thrown when some operation inside the transaction
- * fails.
- */
-public class TransactionException extends Exception {
-
-    /**
-     * Creates an instance of this class. Takes a detail message as parameter.
-     *
-     * @param message message
-     */
-    public TransactionException(String message) {
-        super(message);
-    }
-
-    /**
-     * Creates an instance of this class. Takes a message and a root throwable
-     * as parameter.
-     *
-     * @param message   message
-     * @param rootCause root throwable
-     */
-    public TransactionException(String message, Throwable rootCause) {
-        super(message, rootCause);
-    }
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/UserPerWorkspaceSecurityManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/UserPerWorkspaceSecurityManager.java
index 1212e32..bb253d7 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/UserPerWorkspaceSecurityManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/UserPerWorkspaceSecurityManager.java
@@ -18,9 +18,12 @@ package org.apache.jackrabbit.core;
 
 import org.apache.jackrabbit.api.security.principal.PrincipalManager;
 import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.core.config.LoginModuleConfig;
 import org.apache.jackrabbit.core.config.UserManagerConfig;
 import org.apache.jackrabbit.core.security.authentication.AuthContext;
+import org.apache.jackrabbit.core.security.authentication.AuthContextProvider;
 import org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager;
+import org.apache.jackrabbit.core.security.principal.AbstractPrincipalProvider;
 import org.apache.jackrabbit.core.security.principal.DefaultPrincipalProvider;
 import org.apache.jackrabbit.core.security.principal.PrincipalManagerImpl;
 import org.apache.jackrabbit.core.security.principal.PrincipalProvider;
@@ -52,7 +55,7 @@ import java.util.Set;
  * "security-workspace" that provides user information. Consequently, the
  * UserManager used to retrieve and manipulate user content is always
  * bound to the <code>Session</code> passed to {@link #getUserManager(Session)}.
- * <p/> In addition the default (user-based) principal provider created by
+ * <p> In addition the default (user-based) principal provider created by
  * {@link org.apache.jackrabbit.core.DefaultSecurityManager}
  * cannot be used to retrieve principals. Instead this implementation keeps
  * a distinct pp-registry for each workspace.
@@ -88,8 +91,22 @@ public class UserPerWorkspaceSecurityManager extends DefaultSecurityManager {
                     repo.markWorkspaceActive(wspName);
                 }
 
+                Properties[] moduleConfig = new AuthContextProvider("", ((RepositoryImpl) getRepository()).getConfig().getSecurityConfig().getLoginModuleConfig()).getModuleConfig();
+
                 PrincipalProvider defaultPP = new DefaultPrincipalProvider(systemSession, (UserManagerImpl) getUserManager(systemSession));
-                defaultPP.init(new Properties());
+
+                boolean initialized = false;
+                for (Properties props : moduleConfig) {
+                    //GRANITE-4470: apply config to DefaultPrincipalProvider if there is no explicit PrincipalProvider configured
+                    if (!props.containsKey(LoginModuleConfig.PARAM_PRINCIPAL_PROVIDER_CLASS) && props.containsKey(AbstractPrincipalProvider.MAXSIZE_KEY)) {
+                        defaultPP.init(props);
+                        initialized = true;
+                        break;
+                    }
+                }
+                if (!initialized) {
+                    defaultPP.init(new Properties());
+                }
 
                 p = new WorkspaceBasedPrincipalProviderRegistry(defaultPP);
                 ppRegistries.put(wspName, p);
@@ -196,7 +213,7 @@ public class UserPerWorkspaceSecurityManager extends DefaultSecurityManager {
      * @throws RepositoryException
      */
     @Override
-    protected PrincipalProvider createDefaultPrincipalProvider() throws RepositoryException {
+    protected PrincipalProvider createDefaultPrincipalProvider(Properties[] moduleConfig) throws RepositoryException {
         return null;
     }
 
@@ -219,8 +236,6 @@ public class UserPerWorkspaceSecurityManager extends DefaultSecurityManager {
     @Override
     protected UserManagerImpl createUserManager(SessionImpl session) throws RepositoryException {
         UserManagerConfig umc = getConfig().getUserManagerConfig();
-        Properties params = (umc == null) ? null : umc.getParameters();
-
         UserManagerImpl umgr;
         // in contrast to the DefaultSecurityManager users are not retrieved
         // from a dedicated workspace: the system session of each workspace must
@@ -232,9 +247,9 @@ public class UserPerWorkspaceSecurityManager extends DefaultSecurityManager {
                     Properties.class,
                     MembershipCache.class};
             umgr = (UserPerWorkspaceUserManager) umc.getUserManager(UserPerWorkspaceUserManager.class,
-                    paramTypes, session, adminId, params, getMembershipCache(session));
+                    paramTypes, session, adminId, umc.getParameters(), getMembershipCache(session));
         } else {
-            umgr = new UserPerWorkspaceUserManager(session, adminId, params, getMembershipCache(session));
+            umgr = new UserPerWorkspaceUserManager(session, adminId, null, getMembershipCache(session));
         }
 
         if (umc != null && !(session instanceof SystemSession)) {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/VersionManagerImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/VersionManagerImpl.java
index 555006f..b5af9b8 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/VersionManagerImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/VersionManagerImpl.java
@@ -65,7 +65,7 @@ import org.slf4j.LoggerFactory;
 
 /**
  * Implementation of the {@link javax.jcr.version.VersionManager}.
- * <p/>
+ * <p>
  * This class implements the JCR Version Manager interface but most of the
  * operations are performed in the super classes. this is only cosmetic to
  * avoid huge source files.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/WorkspaceImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/WorkspaceImpl.java
index fd46d4b..c1c6b5f 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/WorkspaceImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/WorkspaceImpl.java
@@ -243,7 +243,7 @@ public class WorkspaceImpl extends AbstractWorkspace
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Always throws <code>UnsupportedRepositoryOperationException</code> since
      * removal of workspaces is currently not supported.
      */
@@ -289,17 +289,17 @@ public class WorkspaceImpl extends AbstractWorkspace
      * Creates a new <code>Workspace</code> with the specified
      * <code>name</code>. The new workspace is empty, meaning it contains only
      * root node.
-     * <p/>
+     * <p>
      * The new workspace can be accessed through a <code>login</code>
      * specifying its name.
-     * <p/>
+     * <p>
      * Throws an <code>AccessDeniedException</code> if the session through which
      * this <code>Workspace</code> object was acquired does not have permission
      * to create the new workspace.
-     * <p/>
+     * <p>
      * Throws an <code>UnsupportedRepositoryOperationException</code> if the repository does
      * not support the creation of workspaces.
-     * <p/>
+     * <p>
      * A <code>RepositoryException</code> is thrown if another error occurs.
      *
      * @param name A <code>String</code>, the name of the new workspace.
@@ -910,7 +910,7 @@ public class WorkspaceImpl extends AbstractWorkspace
     //------------------------------------------< EventStateCollectionFactory >
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Implemented in this object and forwarded rather than {@link #obsMgr}
      * since creation of the latter is lazy.
      */
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/XASessionImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/XASessionImpl.java
index 3a64bad..f2bcaa3 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/XASessionImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/XASessionImpl.java
@@ -23,6 +23,8 @@ import org.apache.jackrabbit.core.security.authentication.AuthContext;
 import org.apache.jackrabbit.core.state.XAItemStateManager;
 import org.apache.jackrabbit.core.version.InternalVersionManager;
 import org.apache.jackrabbit.core.version.InternalXAVersionManager;
+import org.apache.jackrabbit.data.core.InternalXAResource;
+import org.apache.jackrabbit.data.core.TransactionContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -190,7 +192,7 @@ public class XASessionImpl extends SessionImpl
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Two resources belong to the same resource manager if both connections
      * (i.e. sessions) have the same credentials.
      */
@@ -204,7 +206,7 @@ public class XASessionImpl extends SessionImpl
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * If <code>TMNOFLAGS</code> is specified, we create a new transaction
      * context and associate it with this resource.
      * If <code>TMJOIN</code> is specified, this resource should use the
@@ -258,7 +260,7 @@ public class XASessionImpl extends SessionImpl
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * If <code>TMSUCCESS</code> is specified, we disassociate this session
      * from the transaction specified.
      * If <code>TMFAIL</code> is specified, we disassociate this session from
@@ -267,7 +269,7 @@ public class XASessionImpl extends SessionImpl
      * from the transaction specified.
      * All other flags generate an <code>XAException</code> of type
      * <code>XAER_INVAL</code>
-     * <p/>
+     * <p>
      * It is legal for a transaction association to be suspended and then
      * ended (either with <code>TMSUCCESS</code> or <code>TMFAIL</code>)
      * without having been resumed again.
@@ -346,7 +348,7 @@ public class XASessionImpl extends SessionImpl
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * No recovery support yet.
      */
     public Xid[] recover(int flags) throws XAException {
@@ -355,7 +357,7 @@ public class XASessionImpl extends SessionImpl
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * No recovery support yet.
      */
     public void forget(Xid xid) throws XAException {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ZombieHierarchyManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ZombieHierarchyManager.java
index 43e87c4..b0d14e5 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ZombieHierarchyManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/ZombieHierarchyManager.java
@@ -30,7 +30,7 @@ import org.apache.jackrabbit.spi.Name;
  * <code>HierarchyManager</code> implementation that is also able to
  * build/resolve paths of those items that have been moved or removed
  * (i.e. moved to the attic).
- * <p/>
+ * <p>
  * todo make use of path caching
  */
 public class ZombieHierarchyManager extends HierarchyManagerImpl {
@@ -49,7 +49,7 @@ public class ZombieHierarchyManager extends HierarchyManagerImpl {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Delivers state from attic if such exists, otherwise calls base class.
      */
     protected ItemState getItemState(ItemId id)
@@ -64,7 +64,7 @@ public class ZombieHierarchyManager extends HierarchyManagerImpl {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Returns <code>true</code>  if there's state on the attic for the
      * requested item; otherwise delegates to base class.
      */
@@ -79,7 +79,7 @@ public class ZombieHierarchyManager extends HierarchyManagerImpl {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Also allows for removed items.
      */
     protected NodeId getParentId(ItemState state) {
@@ -93,7 +93,7 @@ public class ZombieHierarchyManager extends HierarchyManagerImpl {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Also allows for removed/renamed child node entries.
      */
     protected ChildNodeEntry getChildNodeEntry(NodeState parent,
@@ -116,7 +116,7 @@ public class ZombieHierarchyManager extends HierarchyManagerImpl {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Also allows for removed child node entries.
      */
     protected ChildNodeEntry getChildNodeEntry(NodeState parent,
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cache/CacheManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cache/CacheManager.java
index 4b53f50..9073645 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cache/CacheManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cache/CacheManager.java
@@ -161,7 +161,12 @@ public class CacheManager implements CacheAccessListener {
             if (now < nextLogStats) {
                 return;
             }
-            for (Cache cache : caches.keySet()) {
+            // JCR-3194 avoid ConcurrentModificationException
+            List<Cache> list = new ArrayList<Cache>();
+            synchronized (caches) {
+                list.addAll(caches.keySet());
+            }
+            for (Cache cache : list) {
                 log.debug(cache.getCacheInfoAsString());
             }
             nextLogStats = now + minLogStatsInterval;
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterNode.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterNode.java
index ae35160..60c8dfa 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterNode.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/ClusterNode.java
@@ -80,7 +80,12 @@ public class ClusterNode implements Runnable,
     private static final int STOPPED = 2;
 
     /**
-     * Logger.
+     * Audit logger.
+     */
+    private static Logger auditLogger = LoggerFactory.getLogger("org.apache.jackrabbit.core.audit");
+
+    /**
+     * Default Logger.
      */
     private static Logger log = LoggerFactory.getLogger(ClusterNode.class);
 
@@ -120,6 +125,11 @@ public class ClusterNode implements Runnable,
     private final Mutex syncLock = new Mutex();
 
     /**
+     * Update counter, used in displaying the number of updates in audit log.
+     */
+    private final AtomicInteger updateCount = new AtomicInteger();
+
+    /**
      * Latch used to communicate a stop request to the synchronization thread.
      */
     private final Latch stopLatch = new Latch();
@@ -214,6 +224,7 @@ public class ClusterNode implements Runnable,
         ClusterConfig cc = clusterContext.getClusterConfig();
         clusterNodeId = cc.getId();
         syncDelay = cc.getSyncDelay();
+        stopDelay = cc.getStopDelay();
 
         try {
             journal = cc.getJournal(clusterContext.getNamespaceResolver());
@@ -263,7 +274,7 @@ public class ClusterNode implements Runnable,
      */
     public synchronized void start() throws ClusterException {
         if (status == NONE) {
-            sync();
+            syncOnStartup();
 
             if (!disableAutoSync) {
                 Thread t = new Thread(this, "ClusterNode-" + clusterNodeId);
@@ -294,7 +305,7 @@ public class ClusterNode implements Runnable,
                 String msg = "Periodic sync of journal failed: " + e.getMessage();
                 log.error(msg, e);
             } catch (Exception e) {
-                String msg = "Unexpected error while syncing of journal: " + e.getMessage();
+                String msg = "Unexpected exception while syncing of journal: " + e.getMessage();
                 log.error(msg, e);
             } catch (Error e) {
                 String msg = "Unexpected error while syncing of journal: " + e.getMessage();
@@ -304,12 +315,14 @@ public class ClusterNode implements Runnable,
         }
     }
 
-    /**
+    /** 
      * Synchronize contents from journal.
-     *
+     * 
+     * @param startup indicates if the cluster node is syncing on startup 
+     *        or does a normal sync.
      * @throws ClusterException if an error occurs
      */
-    public void sync() throws ClusterException {
+    private void internalSync(boolean startup) throws ClusterException {
         int count = syncCount.get();
 
         try {
@@ -324,13 +337,32 @@ public class ClusterNode implements Runnable,
             // while we were waiting to acquire the syncLock.
             if (count == syncCount.get()) {
                 syncCount.incrementAndGet();
-                journal.sync();
+                journal.sync(startup);
             }
         } catch (JournalException e) {
             throw new ClusterException(e.getMessage(), e.getCause());
         } finally {
             syncLock.release();
         }
+
+    }
+
+    /**
+     * Synchronize contents from journal.
+     *
+     * @throws ClusterException if an error occurs
+     */
+    public void sync() throws ClusterException {
+        internalSync(false);
+    }
+
+    /**
+     * Synchronize contents from journal when a {@link ClusterNode} starts up.
+     *
+     * @throws ClusterException if an error occurs
+     */
+    public void syncOnStartup() throws ClusterException {
+        internalSync(true);
     }
 
     /**
@@ -573,6 +605,11 @@ public class ClusterNode implements Runnable,
         private static final String ATTRIBUTE_RECORD = "record";
 
         /**
+         * Attribute name used to store the size of the update.
+         */
+        private static final String ATTRIBUTE_UPDATE_SIZE = "updateSize";
+
+        /**
          * Workspace name.
          */
         private final String workspace;
@@ -589,7 +626,7 @@ public class ClusterNode implements Runnable,
         /**
          * {@inheritDoc}
          */
-        public void updateCreated(Update update) {
+        public void updateCreated(Update update) throws ClusterException {
             if (status != STARTED) {
                 log.info("not started: update create ignored.");
                 return;
@@ -598,18 +635,19 @@ public class ClusterNode implements Runnable,
                 Record record = producer.append();
                 update.setAttribute(ATTRIBUTE_RECORD, record);
             } catch (JournalException e) {
-                String msg = "Unable to create log entry.";
-                log.error(msg, e);
+                String msg = "Unable to create log entry: " + e.getMessage();
+                throw new ClusterException(msg, e);
             } catch (Throwable e) {
-                String msg = "Unexpected error while creating log entry.";
-                log.error(msg, e);
+                String msg = "Unexpected error while creating log entry: "
+                        + e.getMessage();
+                throw new ClusterException(msg, e);
             }
         }
 
         /**
          * {@inheritDoc}
          */
-        public void updatePrepared(Update update) {
+        public void updatePrepared(Update update) throws ClusterException {
             if (status != STARTED) {
                 log.info("not started: update prepare ignored.");
                 return;
@@ -633,12 +671,12 @@ public class ClusterNode implements Runnable,
                 succeeded = true;
             } catch (JournalException e) {
                 String msg = "Unable to create log entry: " + e.getMessage();
-                log.error(msg);
+                throw new ClusterException(msg, e);
             } catch (Throwable e) {
                 String msg = "Unexpected error while preparing log entry.";
-                log.error(msg, e);
+                throw new ClusterException(msg, e);
             } finally {
-                if (!succeeded && record != null) {
+                if (!succeeded) {
                     record.cancelUpdate();
                     update.setAttribute(ATTRIBUTE_RECORD, null);
                 }
@@ -649,20 +687,30 @@ public class ClusterNode implements Runnable,
          * {@inheritDoc}
          */
         public void updateCommitted(Update update, String path) {
-            if (status != STARTED) {
-                log.info("not started: update commit ignored.");
-                return;
-            }
             Record record = (Record) update.getAttribute(ATTRIBUTE_RECORD);
             if (record == null) {
-                String msg = "No record prepared.";
-                log.warn(msg);
+                if (status == STARTED) {
+                    log.warn("No record prepared.");
+                } else {
+                    log.info("not started: update commit ignored.");
+                }
                 return;
             }
             try {
-                record.update();
-                setRevision(record.getRevision());
-                log.debug("revision {} {}", record.getRevision(), path);
+                long journalUpdateSize = record.update();
+
+                long recordRevision = record.getRevision();
+                setRevision(recordRevision);
+
+                log.debug("Stored record '{}' to Journal ({})", recordRevision, journalUpdateSize);
+
+                Object updateSizeValue = update.getAttribute(ATTRIBUTE_UPDATE_SIZE);
+                long updateSize = updateSizeValue != null? (Long)updateSizeValue : 0;
+                updateCount.compareAndSet(Integer.MAX_VALUE, 0);
+
+               	auditLogger.info("[{}] {} {} ({})", new Object[]{updateCount.incrementAndGet(), 
+                        record.getRevision(), path, updateSize});
+
             } catch (JournalException e) {
                 String msg = "Unable to commit log entry.";
                 log.error(msg, e);
@@ -678,10 +726,6 @@ public class ClusterNode implements Runnable,
          * {@inheritDoc}
          */
         public void updateCancelled(Update update) {
-            if (status != STARTED) {
-                log.info("not started: update cancel ignored.");
-                return;
-            }
             Record record = (Record) update.getAttribute(ATTRIBUTE_RECORD);
             if (record != null) {
                 record.cancelUpdate();
@@ -867,7 +911,17 @@ public class ClusterNode implements Runnable,
             }
         }
         try {
-            listener.externalUpdate(record.getChanges(), record.getEvents(),
+            List<EventState> eventStates = record.getEvents();
+
+            String path = getFirstUserId(eventStates)
+                    + "@" + workspace
+                    + ":" + EventState.getCommonPath(eventStates, null);
+
+            updateCount.compareAndSet(Integer.MAX_VALUE, 0);
+           	auditLogger.info("[{}] {} {}", new Object[]{updateCount.incrementAndGet(), 
+                    record.getRevision(), path});
+
+            listener.externalUpdate(record.getChanges(), eventStates,
                     record.getTimestamp(), record.getUserData());
         } catch (RepositoryException e) {
             String msg = "Unable to deliver update events: " + e.getMessage();
@@ -1065,4 +1119,11 @@ public class ClusterNode implements Runnable,
             }
         }
     }
+
+    private String getFirstUserId(List<EventState> eventStates) {
+        if (eventStates == null || eventStates.isEmpty()) {
+            return "";
+        }
+        return eventStates.get(0).getUserId();
+    }
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/UpdateEventChannel.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/UpdateEventChannel.java
index 5d83c44..d415be7 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/UpdateEventChannel.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/UpdateEventChannel.java
@@ -25,15 +25,17 @@ public interface UpdateEventChannel {
      * Called when an a update operation has been created.
      *
      * @param update update operation
+     * @throws ClusterException if an error occurs writing to the event channel.
      */
-    void updateCreated(Update update);
+    void updateCreated(Update update) throws ClusterException;
 
     /**
      * Called when an a update operation has been prepared.
      *
      * @param update update operation
+     * @throws ClusterException if an error occurs writing to the event channel.
      */
-    void updatePrepared(Update update);
+    void updatePrepared(Update update) throws ClusterException;
 
     /**
      * Called when an a update operation has been committed.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceEventChannel.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceEventChannel.java
index bb315ee..e4489d7 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceEventChannel.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceEventChannel.java
@@ -1,30 +1,30 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.cluster;
-
-import org.apache.jackrabbit.core.xml.ClonedInputSource;
-
-/**
- * Event channel for reporting workspace change events.
- */
-public interface WorkspaceEventChannel {
-
-    void workspaceCreated(String workspaceName, ClonedInputSource inputSource);
-
-    void setListener(WorkspaceListener listener);
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.cluster;
+
+import org.apache.jackrabbit.core.xml.ClonedInputSource;
+
+/**
+ * Event channel for reporting workspace change events.
+ */
+public interface WorkspaceEventChannel {
+
+    void workspaceCreated(String workspaceName, ClonedInputSource inputSource);
+
+    void setListener(WorkspaceListener listener);
+
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceListener.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceListener.java
index 56d1af6..4275eab 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceListener.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceListener.java
@@ -1,38 +1,38 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.cluster;
-
-import javax.jcr.RepositoryException;
-
-import org.xml.sax.InputSource;
-
-/**
- * Listener for external workspace changes.
- */
-public interface WorkspaceListener {
-
-    /**
-     * Workspace created on another cluster node.
-     *
-     * @param workspaceName
-     * @param configTemplate
-     * @throws RepositoryException
-     */
-    void externalWorkspaceCreated(String workspaceName,
-            InputSource configTemplate) throws RepositoryException;
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.cluster;
+
+import javax.jcr.RepositoryException;
+
+import org.xml.sax.InputSource;
+
+/**
+ * Listener for external workspace changes.
+ */
+public interface WorkspaceListener {
+
+    /**
+     * Workspace created on another cluster node.
+     *
+     * @param workspaceName
+     * @param configTemplate
+     * @throws RepositoryException
+     */
+    void externalWorkspaceCreated(String workspaceName,
+            InputSource configTemplate) throws RepositoryException;
+
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceRecord.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceRecord.java
index 528cd16..6c28f43 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceRecord.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/cluster/WorkspaceRecord.java
@@ -1,206 +1,206 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.cluster;
-
-import java.io.ByteArrayInputStream;
-import java.io.CharArrayReader;
-
-import org.apache.jackrabbit.core.journal.JournalException;
-import org.apache.jackrabbit.core.journal.Record;
-import org.apache.jackrabbit.core.xml.ClonedInputSource;
-import org.xml.sax.InputSource;
-
-/**
- * Record for propagating workspace modifications across the cluster. Currently
- * only workspace creation is propagated because workspace deletion is not yet
- * implemented.
- */
-public class WorkspaceRecord extends ClusterRecord {
-
-    /**
-     * Identifier: NAMESPACE.
-     */
-    static final char IDENTIFIER = 'W';
-
-    /**
-     * Subtype for determining workspace action.
-     */
-    public static final int CREATE_WORKSPACE_ACTION_TYPE = 1;
-
-    /**
-     * Base workspace action
-     */
-    public abstract static class Action {
-        abstract int getType();
-
-        abstract void write(Record record) throws JournalException;
-
-        abstract void read(Record record) throws JournalException;
-    }
-
-    /**
-     * Action for workspace creation.
-     */
-    static final class CreateWorkspaceAction extends Action {
-        private InputSource inputSource;
-        private char[] charArray;
-        private byte[] byteArray;
-
-        @Override
-        int getType() {
-            return CREATE_WORKSPACE_ACTION_TYPE;
-        }
-
-        CreateWorkspaceAction() {
-
-        }
-
-        CreateWorkspaceAction(ClonedInputSource inputSource) {
-            this.inputSource = inputSource;
-            this.charArray = inputSource.getCharacterArray();
-            this.byteArray = inputSource.getByteArray();
-        }
-
-        @Override
-        void write(Record record) throws JournalException {
-            // store the input source
-            record.writeString(inputSource.getEncoding());
-            record.writeString(inputSource.getPublicId());
-            record.writeString(inputSource.getSystemId());
-
-            // save character array if present
-            if (charArray != null) {
-                record.writeBoolean(true);
-                record.writeString(new String(charArray));
-            } else {
-                record.writeBoolean(false);
-            }
-
-            // save the bytearray if present
-            if (byteArray != null) {
-                record.writeBoolean(true);
-                record.writeInt(byteArray.length);
-                record.write(byteArray);
-            } else {
-                record.writeBoolean(false);
-            }
-        }
-
-        @Override
-        void read(Record record) throws JournalException {
-            // restore the input source
-            inputSource = new InputSource();
-            inputSource.setEncoding(record.readString());
-            inputSource.setPublicId(record.readString());
-            inputSource.setSystemId(record.readString());
-
-            if (record.readBoolean()) {
-                charArray = record.readString().toCharArray();
-                inputSource.setCharacterStream(new CharArrayReader(charArray));
-            }
-            if (record.readBoolean()) {
-                final int size = record.readInt();
-                byteArray = new byte[size];
-                record.readFully(byteArray);
-                inputSource.setByteStream(new ByteArrayInputStream(byteArray));
-            }
-        }
-
-        public InputSource getInputSource() {
-            return inputSource;
-        }
-    }
-
-    // current action
-    private Action action;
-
-    /**
-     * Creates a new {@link WorkspaceRecord} for create workspace action.
-     *
-     * @param workspace
-     *            workspace name
-     * @param inputSource
-     *            input source with configuration for the workspace
-     * @param record
-     *            journal record
-     */
-    protected WorkspaceRecord(String workspace, ClonedInputSource inputSource,
-            Record record) {
-        super(record, workspace);
-
-        action = new CreateWorkspaceAction(inputSource);
-    }
-
-    /**
-     * Creates a new empty {@link WorkspaceRecord}.
-     *
-     * @param record
-     */
-    protected WorkspaceRecord(Record record) {
-        super(record);
-    }
-
-    @Override
-    protected void doRead() throws JournalException {
-
-        workspace = record.readString();
-
-        // determine type
-        int action = record.readInt();
-
-        if (action == CREATE_WORKSPACE_ACTION_TYPE) {
-            this.action = new CreateWorkspaceAction();
-        }
-
-        if (this.action != null) {
-            this.action.read(record);
-        } else {
-            throw new JournalException("Unknown workspace action type");
-        }
-    }
-
-    @Override
-    protected void doWrite() throws JournalException {
-
-        record.writeChar(IDENTIFIER);
-
-        record.writeString(workspace);
-
-        // store the type
-        record.writeInt(getActionType());
-
-        if (action != null) {
-            action.write(record);
-        } else {
-            throw new JournalException("Can not write empty workspace action");
-        }
-    }
-
-    public int getActionType() {
-        return action != null ? action.getType() : -1;
-    }
-
-    public CreateWorkspaceAction getCreateWorkspaceAction() {
-        return (CreateWorkspaceAction) action;
-    }
-
-    @Override
-    public void process(ClusterRecordProcessor processor) {
-        processor.process(this);
-    }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.cluster;
+
+import java.io.ByteArrayInputStream;
+import java.io.CharArrayReader;
+
+import org.apache.jackrabbit.core.journal.JournalException;
+import org.apache.jackrabbit.core.journal.Record;
+import org.apache.jackrabbit.core.xml.ClonedInputSource;
+import org.xml.sax.InputSource;
+
+/**
+ * Record for propagating workspace modifications across the cluster. Currently
+ * only workspace creation is propagated because workspace deletion is not yet
+ * implemented.
+ */
+public class WorkspaceRecord extends ClusterRecord {
+
+    /**
+     * Identifier: NAMESPACE.
+     */
+    static final char IDENTIFIER = 'W';
+
+    /**
+     * Subtype for determining workspace action.
+     */
+    public static final int CREATE_WORKSPACE_ACTION_TYPE = 1;
+
+    /**
+     * Base workspace action
+     */
+    public abstract static class Action {
+        abstract int getType();
+
+        abstract void write(Record record) throws JournalException;
+
+        abstract void read(Record record) throws JournalException;
+    }
+
+    /**
+     * Action for workspace creation.
+     */
+    static final class CreateWorkspaceAction extends Action {
+        private InputSource inputSource;
+        private char[] charArray;
+        private byte[] byteArray;
+
+        @Override
+        int getType() {
+            return CREATE_WORKSPACE_ACTION_TYPE;
+        }
+
+        CreateWorkspaceAction() {
+
+        }
+
+        CreateWorkspaceAction(ClonedInputSource inputSource) {
+            this.inputSource = inputSource;
+            this.charArray = inputSource.getCharacterArray();
+            this.byteArray = inputSource.getByteArray();
+        }
+
+        @Override
+        void write(Record record) throws JournalException {
+            // store the input source
+            record.writeString(inputSource.getEncoding());
+            record.writeString(inputSource.getPublicId());
+            record.writeString(inputSource.getSystemId());
+
+            // save character array if present
+            if (charArray != null) {
+                record.writeBoolean(true);
+                record.writeString(new String(charArray));
+            } else {
+                record.writeBoolean(false);
+            }
+
+            // save the bytearray if present
+            if (byteArray != null) {
+                record.writeBoolean(true);
+                record.writeInt(byteArray.length);
+                record.write(byteArray);
+            } else {
+                record.writeBoolean(false);
+            }
+        }
+
+        @Override
+        void read(Record record) throws JournalException {
+            // restore the input source
+            inputSource = new InputSource();
+            inputSource.setEncoding(record.readString());
+            inputSource.setPublicId(record.readString());
+            inputSource.setSystemId(record.readString());
+
+            if (record.readBoolean()) {
+                charArray = record.readString().toCharArray();
+                inputSource.setCharacterStream(new CharArrayReader(charArray));
+            }
+            if (record.readBoolean()) {
+                final int size = record.readInt();
+                byteArray = new byte[size];
+                record.readFully(byteArray);
+                inputSource.setByteStream(new ByteArrayInputStream(byteArray));
+            }
+        }
+
+        public InputSource getInputSource() {
+            return inputSource;
+        }
+    }
+
+    // current action
+    private Action action;
+
+    /**
+     * Creates a new {@link WorkspaceRecord} for create workspace action.
+     *
+     * @param workspace
+     *            workspace name
+     * @param inputSource
+     *            input source with configuration for the workspace
+     * @param record
+     *            journal record
+     */
+    protected WorkspaceRecord(String workspace, ClonedInputSource inputSource,
+            Record record) {
+        super(record, workspace);
+
+        action = new CreateWorkspaceAction(inputSource);
+    }
+
+    /**
+     * Creates a new empty {@link WorkspaceRecord}.
+     *
+     * @param record
+     */
+    protected WorkspaceRecord(Record record) {
+        super(record);
+    }
+
+    @Override
+    protected void doRead() throws JournalException {
+
+        workspace = record.readString();
+
+        // determine type
+        int action = record.readInt();
+
+        if (action == CREATE_WORKSPACE_ACTION_TYPE) {
+            this.action = new CreateWorkspaceAction();
+        }
+
+        if (this.action != null) {
+            this.action.read(record);
+        } else {
+            throw new JournalException("Unknown workspace action type");
+        }
+    }
+
+    @Override
+    protected void doWrite() throws JournalException {
+
+        record.writeChar(IDENTIFIER);
+
+        record.writeString(workspace);
+
+        // store the type
+        record.writeInt(getActionType());
+
+        if (action != null) {
+            action.write(record);
+        } else {
+            throw new JournalException("Can not write empty workspace action");
+        }
+    }
+
+    public int getActionType() {
+        return action != null ? action.getType() : -1;
+    }
+
+    public CreateWorkspaceAction getCreateWorkspaceAction() {
+        return (CreateWorkspaceAction) action;
+    }
+
+    @Override
+    public void process(ClusterRecordProcessor processor) {
+        processor.process(this);
+    }
+
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/BeanConfig.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/BeanConfig.java
index 4c3df82..30fce41 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/BeanConfig.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/BeanConfig.java
@@ -24,6 +24,7 @@ import java.lang.reflect.Modifier;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Properties;
 
@@ -67,6 +68,11 @@ public class BeanConfig {
         BeanConfig.class.getClassLoader();
 
     /**
+     * Factory to create instance from Bean className
+     */
+    private BeanFactory instanceFactory = new SimpleBeanFactory();
+
+    /**
      * The current class loader used by this instance to create instances of
      * configured classes.
      */
@@ -147,6 +153,14 @@ public class BeanConfig {
     }
 
     /**
+     *
+     * @param instanceFactory the {@link BeanFactory} to use to create bean instance
+     */
+    public void setInstanceFactory(BeanFactory instanceFactory) {
+        this.instanceFactory = instanceFactory;
+    }
+
+    /**
      * Returns the class name of the configured bean.
      *
      * @return class name of the bean
@@ -173,59 +187,35 @@ public class BeanConfig {
     @SuppressWarnings("unchecked")
     public <T> T newInstance(Class<T> klass) throws ConfigurationException {
         String cname = getClassName();
-        try {
-            Class<?> objectClass = Class.forName(cname, true, getClassLoader());
-            if (!klass.isAssignableFrom(objectClass)) {
-                throw new ConfigurationException(
-                        "Configured class " + cname
-                        + " does not implement " + klass.getName()
-                        + ". Please fix the repository configuration.");
-            }
-            if (objectClass.getAnnotation(Deprecated.class) != null) {
-                log.warn("{} has been deprecated", cname);
-            }
-
-            // Instantiate the object using the default constructor
-            Object instance = objectClass.newInstance();
+        // Instantiate the object using the default constructor
+        Object instance = instanceFactory.newInstance(klass,this);
+        Class<?> objectClass = instance.getClass();
 
-            // Set all configured bean properties
-            Map<String, Method> setters = getSetters(objectClass);
-            Enumeration<?> enumeration = properties.propertyNames();
-            while (enumeration.hasMoreElements()) {
-                String name = enumeration.nextElement().toString();
-                Method setter = setters.get(name);
-                if (setter != null) {
-                    if (setter.getAnnotation(Deprecated.class) != null) {
-                        log.warn("Parameter {} of {} has been deprecated",
-                                name, cname);
-                    }
-                    String value = properties.getProperty(name);
-                    setProperty(instance, name, setter, value);
-                } else if (validate) {
-                    throw new ConfigurationException(
-                            "Configured class " + cname
-                            + " does not contain a property named " + name);
+        // Set all configured bean properties
+        Map<String, Method> setters = getSetters(objectClass);
+        Enumeration<?> enumeration = properties.propertyNames();
+        while (enumeration.hasMoreElements()) {
+            String name = enumeration.nextElement().toString();
+            Method setter = setters.get(name);
+            if (setter != null) {
+                if (setter.getAnnotation(Deprecated.class) != null) {
+                    log.warn("Parameter {} of {} has been deprecated",
+                            name, cname);
                 }
+                String value = properties.getProperty(name);
+                setProperty(instance, name, setter, value);
+            } else if (validate) {
+                throw new ConfigurationException(
+                        "Configured class " + cname
+                        + " does not contain a property named " + name);
             }
+        }
 
-            if (instance instanceof DatabaseAware) {
-                ((DatabaseAware) instance).setConnectionFactory(connectionFactory);
-            }
-
-            return (T) instance;
-        } catch (ClassNotFoundException e) {
-            throw new ConfigurationException(
-                    "Configured bean implementation class " + cname
-                    + " was not found.", e);
-        } catch (InstantiationException e) {
-            throw new ConfigurationException(
-                    "Configured bean implementation class " + cname
-                    + " can not be instantiated.", e);
-        } catch (IllegalAccessException e) {
-            throw new ConfigurationException(
-                    "Configured bean implementation class " + cname
-                    + " is protected.", e);
+        if (instance instanceof DatabaseAware) {
+            ((DatabaseAware) instance).setConnectionFactory(connectionFactory);
         }
+
+        return (T) instance;
     }
 
     private Map<String, Method> getSetters(Class<?> klass) {
@@ -238,7 +228,7 @@ public class BeanConfig {
                     && Void.TYPE.equals(method.getReturnType())
                     && method.getParameterTypes().length == 1) {
                 methods.put(
-                        name.substring(3, 4).toLowerCase() + name.substring(4),
+                        name.substring(3, 4).toLowerCase(Locale.ENGLISH) + name.substring(4),
                         method);
             }
         }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/BeanConfigVisitor.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/BeanConfigVisitor.java
new file mode 100644
index 0000000..2591c74
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/BeanConfigVisitor.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.config;
+
+/**
+ * A BeanConfig visitor which is invoked upon creation of BeanConfig before any
+ * instance is created from that bean configuration
+ */
+public interface BeanConfigVisitor {
+    void visit(BeanConfig config);
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/BeanFactory.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/BeanFactory.java
new file mode 100644
index 0000000..90a10bb
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/BeanFactory.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.config;
+
+
+public interface BeanFactory {
+    Object newInstance(Class<?> klass, BeanConfig config) throws ConfigurationException;
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ClusterConfig.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ClusterConfig.java
index a242032..95d8410 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ClusterConfig.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ClusterConfig.java
@@ -38,6 +38,11 @@ public class ClusterConfig implements JournalFactory {
     private final long syncDelay;
 
     /**
+     * Stop delay.
+     */
+    private final long stopDelay;
+
+    /**
      * Journal factory.
      */
     private final JournalFactory jf;
@@ -47,11 +52,25 @@ public class ClusterConfig implements JournalFactory {
      *
      * @param id custom cluster node id
      * @param syncDelay syncDelay, in milliseconds
-     * @param jc journal configuration
+     * @param jf journal factory
      */
     public ClusterConfig(String id, long syncDelay, JournalFactory jf) {
+        this(id, syncDelay, -1, jf);
+    }
+
+    /**
+     * Creates a new cluster configuration.
+     *
+     * @param id custom cluster node id
+     * @param syncDelay syncDelay, in milliseconds
+     * @param stopDelay stopDelay in milliseconds
+     * @param jf journal factory
+     */
+    public ClusterConfig(String id, long syncDelay,
+                         long stopDelay, JournalFactory jf) {
         this.id = id;
         this.syncDelay = syncDelay;
+        this.stopDelay = stopDelay < 0 ? syncDelay * 10 : stopDelay;
         this.jf = jf;
     }
 
@@ -74,6 +93,13 @@ public class ClusterConfig implements JournalFactory {
     }
 
     /**
+     * @return stopDelay the stopDelay configuration attribute value.
+     */
+    public long getStopDelay() {
+        return stopDelay;
+    }
+
+    /**
      * Returns an initialized journal instance.
      *
      * @param resolver namespace resolver
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ConfigurationEntityResolver.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ConfigurationEntityResolver.java
index 2d7b0bc..612c218 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ConfigurationEntityResolver.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ConfigurationEntityResolver.java
@@ -30,6 +30,7 @@ import org.xml.sax.SAXException;
  * This simple resolver contains mappings for the following
  * public identifiers used for the Jackrabbit configuration files:
  * <ul>
+ * <li><code>-//The Apache Software Foundation//DTD Jackrabbit 2.6//EN</code></li>
  * <li><code>-//The Apache Software Foundation//DTD Jackrabbit 2.4//EN</code></li>
  * <li><code>-//The Apache Software Foundation//DTD Jackrabbit 2.0//EN</code></li>
  * <li><code>-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN</code></li>
@@ -41,6 +42,7 @@ import org.xml.sax.SAXException;
  * <p>
  * Also the following system identifiers are mapped to local resources:
  * <ul>
+ * <li><code>http://jackrabbit.apache.org/dtd/repository-2.6.dtd</code></li>
  * <li><code>http://jackrabbit.apache.org/dtd/repository-2.4.dtd</code></li>
  * <li><code>http://jackrabbit.apache.org/dtd/repository-2.0.dtd</code></li>
  * <li><code>http://jackrabbit.apache.org/dtd/repository-1.6.dtd</code></li>
@@ -75,6 +77,21 @@ public class ConfigurationEntityResolver implements EntityResolver {
      * Creates the singleton instance of this class.
      */
     private ConfigurationEntityResolver() {
+        // Apache Jackrabbit 2.6 DTD
+        publicIds.put(
+                "-//The Apache Software Foundation//DTD Jackrabbit 2.6//EN",
+                "repository-2.6.dtd");
+        systemIds.put(
+                "http://jackrabbit.apache.org/dtd/repository-2.6.dtd",
+                "repository-2.6.dtd");
+        publicIds.put(
+                "-//The Apache Software Foundation//DTD Jackrabbit 2.6 Elements//EN",
+                "repository-2.6-elements.dtd");
+        systemIds.put(
+                "http://jackrabbit.apache.org/dtd/repository-2.6-elements.dtd",
+                "repository-2.6-elements.dtd");
+
+        
         // Apache Jackrabbit 2.4 DTD
         publicIds.put(
                 "-//The Apache Software Foundation//DTD Jackrabbit 2.4//EN",
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ConfigurationParser.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ConfigurationParser.java
index 24bfbbf..a010c5c 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ConfigurationParser.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ConfigurationParser.java
@@ -109,13 +109,7 @@ public class ConfigurationParser {
         // Bean configuration element
         Element element = getElement(parent, name);
 
-        // Bean implementation class
-        String className = getAttribute(element, CLASS_ATTRIBUTE);
-
-        // Bean properties
-        Properties properties = parseParameters(element);
-
-        return new BeanConfig(className, properties);
+        return parseBeanConfig(element);
     }
 
     /**
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/NoOpConfigVisitor.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/NoOpConfigVisitor.java
new file mode 100644
index 0000000..995bc7c
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/NoOpConfigVisitor.java
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.config;
+
+public class NoOpConfigVisitor implements BeanConfigVisitor{
+    @Override
+    public void visit(BeanConfig config) {
+
+    }
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfig.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfig.java
index a1850d6..b87e310 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfig.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfig.java
@@ -707,7 +707,7 @@ public class RepositoryConfig
     /**
      * Creates a new workspace configuration with the specified name and the
      * specified workspace <code>template</.
-     * <p/>
+     * <p>
      * This method creates a workspace configuration subdirectory,
      * copies the workspace configuration template into it, and finally
      * adds the created workspace configuration to the repository.
@@ -876,7 +876,7 @@ public class RepositoryConfig
      * method uses the provided workspace <code>template</code> to create the
      * repository config instead of the template that is present in the
      * repository configuration.
-     * <p/>
+     * <p>
      * This method creates a workspace configuration subdirectory,
      * copies the workspace configuration template into it, and finally
      * adds the created workspace configuration to the repository.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfigurationParser.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfigurationParser.java
index 30d9d78..8caef04 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfigurationParser.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfigurationParser.java
@@ -16,10 +16,22 @@
  */
 package org.apache.jackrabbit.core.config;
 
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+import java.util.UUID;
+
+import javax.jcr.RepositoryException;
+import javax.xml.parsers.DocumentBuilderFactory;
+
 import org.apache.commons.io.FileUtils;
 import org.apache.jackrabbit.core.cluster.ClusterNode;
 import org.apache.jackrabbit.core.data.DataStore;
 import org.apache.jackrabbit.core.data.DataStoreFactory;
+import org.apache.jackrabbit.core.data.MultiDataStore;
+import org.apache.jackrabbit.core.data.MultiDataStoreAware;
 import org.apache.jackrabbit.core.fs.FileSystem;
 import org.apache.jackrabbit.core.fs.FileSystemException;
 import org.apache.jackrabbit.core.fs.FileSystemFactory;
@@ -38,20 +50,13 @@ import org.apache.jackrabbit.core.util.RepositoryLockMechanism;
 import org.apache.jackrabbit.core.util.RepositoryLockMechanismFactory;
 import org.apache.jackrabbit.core.util.db.ConnectionFactory;
 import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
+import org.w3c.dom.Document;
 import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 import org.xml.sax.InputSource;
 
-import java.io.File;
-import java.io.IOException;
-import java.util.Properties;
-import java.util.UUID;
-import java.util.List;
-import java.util.ArrayList;
-
-import javax.jcr.RepositoryException;
-
 /**
  * Configuration parser. This class is used to parse the repository and
  * workspace configuration files.
@@ -173,6 +178,9 @@ public class RepositoryConfigurationParser extends ConfigurationParser {
     /** Name of the syncDelay configuration attribute. */
     public static final String SYNC_DELAY_ATTRIBUTE = "syncDelay";
 
+    /** Name of the stopDelay configuration attribute. */
+    public static final String STOP_DELAY_ATTRIBUTE = "stopDelay";
+
     /** Name of the default search index implementation class. */
     public static final String DEFAULT_QUERY_HANDLER =
         "org.apache.jackrabbit.core.query.lucene.SearchIndex";
@@ -180,9 +188,21 @@ public class RepositoryConfigurationParser extends ConfigurationParser {
     /** Name of the clustered configuration attribute. */
     public static final String CLUSTERED_ATTRIBUTE = "clustered";
 
+    /** Name of the primary DataStore class attribute. */
+    public static final String PRIMARY_DATASTORE_ATTRIBUTE = "primary";
+
+    /** Name of the archive DataStore class attribute. */
+    public static final String ARCHIVE_DATASTORE_ATTRIBUTE = "archive";
+
     /** Default synchronization delay, in milliseconds. */
     public static final String DEFAULT_SYNC_DELAY = "5000";
 
+    /**
+     * Default stop delay, in milliseconds or -1 if the default is derived
+     * from the sync delay.
+     */
+    public static final String DEFAULT_STOP_DELAY = "-1";
+
     /** Name of the workspace specific security configuration element */
     private static final String WSP_SECURITY_ELEMENT = "WorkspaceSecurity";
 
@@ -203,6 +223,10 @@ public class RepositoryConfigurationParser extends ConfigurationParser {
      */
     protected final ConnectionFactory connectionFactory;
 
+    protected BeanFactory beanFactory = new SimpleBeanFactory();
+
+    protected BeanConfigVisitor configVisitor = new NoOpConfigVisitor();
+
     /**
      * Element specifying the class of principals used to retrieve the userID
      * in the 'class' attribute.
@@ -355,6 +379,8 @@ public class RepositoryConfigurationParser extends ConfigurationParser {
     protected BeanConfig parseBeanConfig(Element parent, String name) throws ConfigurationException {
         BeanConfig cfg = super.parseBeanConfig(parent, name);
         cfg.setConnectionFactory(connectionFactory);
+        cfg.setInstanceFactory(beanFactory);
+        configVisitor.visit(cfg);
         return cfg;
     }
 
@@ -365,6 +391,8 @@ public class RepositoryConfigurationParser extends ConfigurationParser {
     protected BeanConfig parseBeanConfig(Element element) throws ConfigurationException {
         BeanConfig cfg = super.parseBeanConfig(element);
         cfg.setConnectionFactory(connectionFactory);
+        cfg.setInstanceFactory(beanFactory);
+        configVisitor.visit(cfg);
         return cfg;
     }
 
@@ -378,12 +406,12 @@ public class RepositoryConfigurationParser extends ConfigurationParser {
      *     <LoginModule ... (optional)>
      *   </Security>
      * </pre>
-     * <p/>
+     * <p>
      * The <code>SecurityManager</code>, the <code>AccessManager</code>
      * and <code>LoginModule</code> are all
      * {@link #parseBeanConfig(Element,String) bean configuration}
      * elements.
-     * <p/>
+     * <p>
      * The login module is an optional feature of repository configuration.
      *
      * @param security the <security> element.
@@ -553,8 +581,7 @@ public class RepositoryConfigurationParser extends ConfigurationParser {
         String home = getVariables().getProperty(WORKSPACE_HOME_VARIABLE);
 
         // Workspace name
-        String name =
-            getAttribute(root, NAME_ATTRIBUTE, new File(home).getName());
+        String name = getAttribute(root, "name", new File(home).getName());
 
         // Clustered attribute
         boolean clustered = Boolean.valueOf(
@@ -582,12 +609,22 @@ public class RepositoryConfigurationParser extends ConfigurationParser {
         // workspace specific security configuration
         WorkspaceSecurityConfig workspaceSecurityConfig = tmpParser.parseWorkspaceSecurityConfig(root);
 
-        // optinal config for import handling
+        // optional config for import handling
         ImportConfig importConfig = tmpParser.parseImportConfig(root);
 
+        // default lock timeout
+        String to = getAttribute(root, "defaultLockTimeout", new Long(Long.MAX_VALUE).toString());
+        long defaultLockTimeout;
+        try {
+            defaultLockTimeout = Long.parseLong(to);
+        }
+        catch (NumberFormatException ex) {
+            throw new ConfigurationException("defaultLockTimeout must be an integer value", ex);
+        }
+
         return new WorkspaceConfig(
                 home, name, clustered, fsf, pmc, qhf,
-                ismLockingFactory, workspaceSecurityConfig, importConfig);
+                ismLockingFactory, workspaceSecurityConfig, importConfig, defaultLockTimeout);
     }
 
     /**
@@ -600,16 +637,16 @@ public class RepositoryConfigurationParser extends ConfigurationParser {
      *     <FileSystem ...>
      *   </Search>
      * </pre>
-     * <p/>
+     * <p>
      * Both the <code>SearchIndex</code> and <code>FileSystem</code>
      * elements are {@link #parseBeanConfig(Element,String) bean configuration}
      * elements. If the search implementation class is not given, then
      * a default implementation is used.
-     * <p/>
+     * <p>
      * The search index is an optional feature of workspace configuration.
      * If the search configuration element is not found, then this method
      * returns <code>null</code>.
-     * <p/>
+     * <p>
      * The FileSystem element in a search index configuration is optional.
      * However some implementations may require a FileSystem.
      *
@@ -715,8 +752,7 @@ public class RepositoryConfigurationParser extends ConfigurationParser {
                     if (IMPORT_PNI_ELEMENT.equals(child.getNodeName()) ||
                             IMPORT_PPI_ELEMENT.equals(child.getNodeName()) ||
                             IMPORT_PII_ELEMENT.equals(child.getNodeName())) {
-                        String className = getAttribute((Element) child, CLASS_ATTRIBUTE);
-                        BeanConfig bc = new BeanConfig(className, parseParameters((Element) child));
+                        BeanConfig bc = parseBeanConfig((Element) child);
                         bc.setValidate(false);
                         protectedItemImporters.add(bc);
                     } // else: some other entry -> ignore.
@@ -810,10 +846,10 @@ public class RepositoryConfigurationParser extends ConfigurationParser {
      *     <Journal ...>
      *   </Journal>
      * </pre>
-     * <p/>
+     * <p>
      * <code>Cluster</code> is a {@link #parseBeanConfig(Element,String) bean configuration}
      * element.
-     * <p/>
+     * <p>
      * Clustering is an optional feature. If the cluster element is not found, then this
      * method returns <code>null</code>.
      *
@@ -855,9 +891,11 @@ public class RepositoryConfigurationParser extends ConfigurationParser {
 
                 long syncDelay = Long.parseLong(replaceVariables(getAttribute(
                         element, SYNC_DELAY_ATTRIBUTE, DEFAULT_SYNC_DELAY)));
+                long stopDelay = Long.parseLong(replaceVariables(getAttribute(
+                        element, STOP_DELAY_ATTRIBUTE, "-1")));
 
                 JournalFactory jf = getJournalFactory(element, home, id);
-                return new ClusterConfig(id, syncDelay, jf);
+                return new ClusterConfig(id, syncDelay, stopDelay, jf);
             }
         }
         return null;
@@ -871,7 +909,7 @@ public class RepositoryConfigurationParser extends ConfigurationParser {
      *     ...
      *   </Journal>
      * </pre>
-     * <p/>
+     * <p>
      * <code>Journal</code> is a {@link #parseBeanConfig(Element,String) bean configuration}
      * element.
      *
@@ -918,7 +956,7 @@ public class RepositoryConfigurationParser extends ConfigurationParser {
      *     </DataSource>
      *   </DataSources>
      * </pre>
-     * <p/>
+     * <p>
      * @param parent the parent of the DataSources element
      * @return a {@link DataSourceConfig} for the repository
      * @throws ConfigurationException on error
@@ -957,7 +995,20 @@ public class RepositoryConfigurationParser extends ConfigurationParser {
      *     ...
      *   </DataStore>
      * </pre>
-     * <p/>
+     * Its also possible to configure a multi data store. The configuration uses following format:
+     * <pre>
+     *   <DataStore class="org.apache.jackrabbit.core.data.MultiDataStore">
+     *     <param name="primary" value="org.apache.jackrabbit.core.data.db.XXDataStore">
+     *         <param name="..." value="...">
+     *         ...
+     *     </param>
+     *     <param name="archive" value="org.apache.jackrabbit.core.data.db.XXDataStore">
+     *         <param name="..." value="...">
+     *         ...
+     *     </param>
+     *   </DataStore>
+     * </pre>
+     * <p>
      * <code>DataStore</code> is a {@link #parseBeanConfig(Element,String) bean configuration}
      * element.
      *
@@ -976,9 +1027,54 @@ public class RepositoryConfigurationParser extends ConfigurationParser {
                     Node child = children.item(i);
                     if (child.getNodeType() == Node.ELEMENT_NODE
                             && DATA_STORE_ELEMENT.equals(child.getNodeName())) {
-                        BeanConfig bc =
-                            parseBeanConfig(parent, DATA_STORE_ELEMENT);
+                        BeanConfig bc = parseBeanConfig(parent, DATA_STORE_ELEMENT);
+                        bc.setValidate(false);
                         DataStore store = bc.newInstance(DataStore.class);
+                        if (store instanceof MultiDataStore) {
+                            DataStore primary = null;
+                            DataStore archive = null;
+                            NodeList subParamNodes = child.getChildNodes();
+                            for (int x = 0; x < subParamNodes.getLength(); x++) {
+                                Node paramNode = subParamNodes.item(x);
+                                if (paramNode.getNodeType() == Node.ELEMENT_NODE 
+                                        && (PRIMARY_DATASTORE_ATTRIBUTE.equals(paramNode.getAttributes().getNamedItem("name").getNodeValue())
+                                                || ARCHIVE_DATASTORE_ATTRIBUTE.equals(paramNode.getAttributes().getNamedItem("name").getNodeValue()))) {
+                                    try {
+                                        Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+                                        Element newParent = document.createElement("parent");
+                                        document.appendChild(newParent);
+                                        Element datastoreElement = document.createElement(DATA_STORE_ELEMENT);
+                                        newParent.appendChild(datastoreElement);
+                                        NodeList childNodes = paramNode.getChildNodes();
+                                        for (int y = 0; childNodes.getLength() > y; y++) {
+                                            datastoreElement.appendChild(document.importNode(childNodes.item(y), true));
+                                        }
+                                        NamedNodeMap attributes = paramNode.getAttributes();
+                                        for (int z = 0; attributes.getLength() > z; z++) {
+                                            Node item = attributes.item(z);
+                                            datastoreElement.setAttribute(CLASS_ATTRIBUTE, item.getNodeValue());
+                                        }
+                                        DataStore subDataStore = getDataStoreFactory(newParent, directory).getDataStore();
+                                        if (!MultiDataStoreAware.class.isAssignableFrom(subDataStore.getClass())) {
+                                            throw new ConfigurationException("Only MultiDataStoreAware datastore's can be used within a MultiDataStore.");
+                                        }
+                                        String type = getAttribute((Element) paramNode, NAME_ATTRIBUTE);
+                                        if (PRIMARY_DATASTORE_ATTRIBUTE.equals(type)) {
+                                            primary = subDataStore;
+                                        } else if (ARCHIVE_DATASTORE_ATTRIBUTE.equals(type)) {
+                                            archive = subDataStore;
+                                        }
+                                    } catch (Exception e) {
+                                        throw new ConfigurationException("Failed to parse the MultiDataStore element.", e);
+                                    }
+                                }
+                            }
+                            if (primary == null || archive == null) {
+                                throw new ConfigurationException("A MultiDataStore must have configured a primary and archive datastore");
+                            }
+                            ((MultiDataStore) store).setPrimaryDataStore(primary);
+                            ((MultiDataStore) store).setArchiveDataStore(archive);
+                        }
                         store.init(directory);
                         return store;
                     }
@@ -997,7 +1093,7 @@ public class RepositoryConfigurationParser extends ConfigurationParser {
      *     ...
      *   </RepositoryLockMechanism>
      * </pre>
-     * <p/>
+     * <p>
      * <code>RepositoryLockMechanism</code> is a
      * {@link #parseBeanConfig(Element,String) bean configuration} element.
      *
@@ -1076,4 +1172,12 @@ public class RepositoryConfigurationParser extends ConfigurationParser {
         };
     }
 
+
+    public void setBeanFactory(BeanFactory beanFactory) {
+        this.beanFactory = beanFactory;
+    }
+
+    public void setConfigVisitor(BeanConfigVisitor configVisitor) {
+        this.configVisitor = configVisitor;
+    }
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/SimpleBeanFactory.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/SimpleBeanFactory.java
new file mode 100644
index 0000000..d812300
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/SimpleBeanFactory.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.config;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SimpleBeanFactory implements BeanFactory {
+    private Logger log = LoggerFactory.getLogger(getClass());
+
+    @Override
+    public Object newInstance(Class<?> klass, BeanConfig config) throws ConfigurationException{
+        String cname = config.getClassName();
+        try {
+            Class<?> objectClass = Class.forName(cname, true, config.getClassLoader());
+            if (!klass.isAssignableFrom(objectClass)) {
+                throw new ConfigurationException(
+                        "Configured class " + cname
+                                + " does not implement " + klass.getName()
+                                + ". Please fix the repository configuration.");
+            }
+            if (objectClass.getAnnotation(Deprecated.class) != null) {
+                log.warn("{} has been deprecated", cname);
+            }
+
+            // Instantiate the object using the default constructor
+            return objectClass.newInstance();
+        } catch (ClassNotFoundException e) {
+            throw new ConfigurationException(
+                    "Configured bean implementation class " + cname
+                            + " was not found.", e);
+        } catch (InstantiationException e) {
+            throw new ConfigurationException(
+                    "Configured bean implementation class " + cname
+                            + " can not be instantiated.", e);
+        } catch (IllegalAccessException e) {
+            throw new ConfigurationException(
+                    "Configured bean implementation class " + cname
+                            + " is protected.", e);
+        }
+    }
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/WorkspaceConfig.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/WorkspaceConfig.java
index 99a8c73..eb91948 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/WorkspaceConfig.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/WorkspaceConfig.java
@@ -29,7 +29,7 @@ import org.apache.jackrabbit.core.state.ISMLockingFactory;
 /**
  * Workspace configuration. This configuration class is used to create
  * configured workspace objects.
- * <p/>
+ * <p>
  * The contained configuration information are: the home directory and name of
  * the workspace, the file system, the persistence manager, the search index and
  * the item state manager locking configuration. The search index and the item
@@ -88,6 +88,11 @@ public class WorkspaceConfig
     private final ImportConfig importConfig;
 
     /**
+     * Default lock timeout in seconds.
+     */
+    private final long defaultLockTimeout;
+
+    /**
      * Creates a workspace configuration object.
      *
      * @param home home directory
@@ -104,7 +109,7 @@ public class WorkspaceConfig
                            QueryHandlerFactory qhf,
                            ISMLockingFactory ismLockingFactory,
                            WorkspaceSecurityConfig workspaceSecurityConfig) {
-        this(home, name, clustered, fsf, pmc, qhf, ismLockingFactory, workspaceSecurityConfig, null);
+        this(home, name, clustered, fsf, pmc, qhf, ismLockingFactory, workspaceSecurityConfig, null, Long.MAX_VALUE);
     }
 
     /**
@@ -125,6 +130,25 @@ public class WorkspaceConfig
                            ISMLockingFactory ismLockingFactory,
                            WorkspaceSecurityConfig workspaceSecurityConfig,
                            ImportConfig importConfig) {
+        this(home, name, clustered, fsf, pmc, qhf, ismLockingFactory, workspaceSecurityConfig, importConfig, Long.MAX_VALUE);
+    }
+
+    /**
+     * Creates a workspace configuration object.
+     *
+     * @param home home directory
+     * @param name workspace name
+     * @param clustered
+     * @param fsf file system factory
+     * @param pmc persistence manager configuration
+     * @param qhf query handler factory, or <code>null</code> if not configured
+     * @param ismLockingFactory the item state manager locking factory
+     * @param workspaceSecurityConfig the workspace specific security configuration.
+     * @param defaultLockTimeout default timeout for locks (in seconds)
+     */
+    public WorkspaceConfig(String home, String name, boolean clustered, FileSystemFactory fsf,
+            PersistenceManagerConfig pmc, QueryHandlerFactory qhf, ISMLockingFactory ismLockingFactory,
+            WorkspaceSecurityConfig workspaceSecurityConfig, ImportConfig importConfig, long defaultLockTimeout) {
         this.home = home;
         this.name = name;
         this.clustered = clustered;
@@ -134,6 +158,7 @@ public class WorkspaceConfig
         this.ismLockingFactory = ismLockingFactory;
         this.workspaceSecurityConfig = workspaceSecurityConfig;
         this.importConfig = importConfig;
+        this.defaultLockTimeout = defaultLockTimeout;
     }
 
     /**
@@ -165,6 +190,17 @@ public class WorkspaceConfig
     }
 
     /**
+     * Returns the default lock timeout in number of seconds or
+     * <code>Long.MAX_VALUE</code> when not specified.
+     * 
+     * @return default lock timeout in number of seconds or
+     *         <code>Long.MAX_VALUE</code> when not specified
+     */
+    public long getDefaultLockTimeout() {
+        return defaultLockTimeout;
+    }
+
+    /**
      * Creates and returns the configured workspace locking strategy.
      *
      * @return the configured {@link ISMLocking}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/AbstractDataRecord.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/AbstractDataRecord.java
deleted file mode 100644
index 24a273e..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/AbstractDataRecord.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.jackrabbit.core.data;
-
-/**
- * Abstract data record base class. This base class contains only
- * a reference to the data identifier of the record and implements
- * the standard {@link Object} equality, hash code, and string
- * representation methods based on the identifier.
- */
-public abstract class AbstractDataRecord implements DataRecord {
-
-    /**
-     * The binary identifier;
-     */
-    private final DataIdentifier identifier;
-
-    /**
-     * Creates a data record with the given identifier.
-     *
-     * @param identifier data identifier
-     */
-    public AbstractDataRecord(DataIdentifier identifier) {
-        this.identifier = identifier;
-    }
-
-    /**
-     * Returns the data identifier.
-     *
-     * @return data identifier
-     */
-    public DataIdentifier getIdentifier() {
-        return identifier;
-    }
-
-    /**
-     * Returns the string representation of the data identifier.
-     *
-     * @return string representation
-     */
-    public String toString() {
-        return identifier.toString();
-    }
-
-    /**
-     * Checks if the given object is a data record with the same identifier
-     * as this one.
-     *
-     * @param object other object
-     * @return <code>true</code> if the other object is a data record and has
-     *         the same identifier as this one, <code>false</code> otherwise
-     */
-    public boolean equals(Object object) {
-        return (object instanceof DataRecord)
-            && identifier.equals(((DataRecord) object).getIdentifier());
-    }
-
-    /**
-     * Returns the hash code of the data identifier.
-     *
-     * @return hash code
-     */
-    public int hashCode() {
-        return identifier.hashCode();
-    }
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/DataIdentifier.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/DataIdentifier.java
deleted file mode 100644
index f7b6aca..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/DataIdentifier.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.data;
-
-import java.io.Serializable;
-
-/**
- * Opaque data identifier used to identify records in a data store.
- * All identifiers must be serializable and implement the standard
- * object equality and hash code methods.
- */
-public final class DataIdentifier implements Serializable {
-
-    /**
-     * Serial version UID.
-     */
-    private static final long serialVersionUID = -9197191401131100016L;
-
-    /**
-     * Array of hexadecimal digits.
-     */
-    private static final char[] HEX = "0123456789abcdef".toCharArray();
-
-    /**
-     * Data identifier.
-     */
-    private final String identifier;
-
-    /**
-     * Creates a data identifier from the given string.
-     *
-     * @param identifier data identifier
-     */
-    public DataIdentifier(String identifier) {
-        this.identifier = identifier;
-    }
-
-    /**
-     * Creates a data identifier from the hexadecimal string
-     * representation of the given bytes.
-     *
-     * @param identifier data identifier
-     */
-    public DataIdentifier(byte[] identifier) {
-        char[] buffer = new char[identifier.length * 2];
-        for (int i = 0; i < identifier.length; i++) {
-            buffer[2 * i] = HEX[(identifier[i] >> 4) & 0x0f];
-            buffer[2 * i + 1] = HEX[identifier[i] & 0x0f];
-        }
-        this.identifier = new String(buffer);
-    }
-
-    //-------------------------------------------------------------< Object >
-
-    /**
-     * Returns the identifier string.
-     *
-     * @return identifier string
-     */
-    public String toString() {
-        return identifier;
-    }
-
-    /**
-     * Checks if the given object is a data identifier and has the same
-     * string representation as this one.
-     *
-     * @param object other object
-     * @return <code>true</code> if the given object is the same identifier,
-     *         <code>false</code> otherwise
-     */
-    public boolean equals(Object object) {
-        return (object instanceof DataIdentifier)
-            && identifier.equals(object.toString());
-    }
-
-    /**
-     * Returns the hash code of the identifier string.
-     *
-     * @return hash code
-     */
-    public int hashCode() {
-        return identifier.hashCode();
-    }
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/DataRecord.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/DataRecord.java
deleted file mode 100644
index c9e24c5..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/DataRecord.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.data;
-
-import java.io.InputStream;
-
-/**
- * Immutable data record that consists of a binary stream.
- */
-public interface DataRecord {
-
-    /**
-     * Returns the identifier of this record.
-     *
-     * @return data identifier
-     */
-    DataIdentifier getIdentifier();
-
-    /**
-     * Returns the length of the binary stream in this record.
-     *
-     * @return length of the binary stream
-     * @throws DataStoreException if the record could not be accessed
-     */
-    long getLength() throws DataStoreException;
-
-    /**
-     * Returns the the binary stream in this record.
-     *
-     * @return binary stream
-     * @throws DataStoreException if the record could not be accessed
-     */
-    InputStream getStream() throws DataStoreException;
-
-    /**
-     * Returns the last modified of the record.
-     * 
-     * @return last modified time of the binary stream
-     */
-    long getLastModified();
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/DataStore.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/DataStore.java
deleted file mode 100644
index 8174657..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/DataStore.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.data;
-
-import java.io.InputStream;
-import java.util.Iterator;
-
-import javax.jcr.RepositoryException;
-
-/**
- * Append-only store for binary streams. A data store consists of a number
- * of identifiable data records that each contain a distinct binary stream.
- * New binary streams can be added to the data store, but existing streams
- * are never removed or modified.
- * <p>
- * A data store should be fully thread-safe, i.e. it should be possible to
- * add and access data records concurrently. Optimally even separate processes
- * should be able to concurrently access the data store with zero interprocess
- * synchronization.
- */
-public interface DataStore {
-
-    /**
-     * Check if a record for the given identifier exists, and return it if yes.
-     * If no record exists, this method returns null.
-     * 
-     * @param identifier data identifier
-     * @return the record if found, and null if not
-     */
-    DataRecord getRecordIfStored(DataIdentifier identifier) throws DataStoreException;
-
-    /**
-     * Returns the identified data record. The given identifier should be
-     * the identifier of a previously saved data record. Since records are
-     * never removed, there should never be cases where the identified record
-     * is not found. Abnormal cases like that are treated as errors and
-     * handled by throwing an exception.
-     *
-     * @param identifier data identifier
-     * @return identified data record
-     * @throws DataStoreException if the data store could not be accessed,
-     *                     or if the given identifier is invalid
-     */
-    DataRecord getRecord(DataIdentifier identifier) throws DataStoreException;
-
-    /**
-     * Creates a new data record. The given binary stream is consumed and
-     * a binary record containing the consumed stream is created and returned.
-     * If the same stream already exists in another record, then that record
-     * is returned instead of creating a new one.
-     * <p>
-     * The given stream is consumed and <strong>not closed</strong> by this
-     * method. It is the responsibility of the caller to close the stream.
-     * A typical call pattern would be:
-     * <pre>
-     *     InputStream stream = ...;
-     *     try {
-     *         record = store.addRecord(stream);
-     *     } finally {
-     *         stream.close();
-     *     }
-     * </pre>
-     *
-     * @param stream binary stream
-     * @return data record that contains the given stream
-     * @throws DataStoreException if the data store could not be accessed
-     */
-    DataRecord addRecord(InputStream stream) throws DataStoreException;
-
-    /**
-     * From now on, update the modified date of an object even when accessing it.
-     * Usually, the modified date is only updated when creating a new object,
-     * or when a new link is added to an existing object. When this setting is enabled,
-     * even getLength() will update the modified date.
-     *
-     * @param before - update the modified date to the current time if it is older than this value
-     */
-    void updateModifiedDateOnAccess(long before);
-
-    /**
-     * Delete objects that have a modified date older than the specified date.
-     *
-     * @param min the minimum time
-     * @return the number of data records deleted
-     * @throws DataStoreException
-     */
-    int deleteAllOlderThan(long min) throws DataStoreException;
-
-    /**
-     * Get all identifiers.
-     *
-     * @return an iterator over all DataIdentifier objects
-     * @throws DataStoreException if the list could not be read
-     */
-    Iterator<DataIdentifier> getAllIdentifiers() throws DataStoreException;
-
-    /**
-     * Initialized the data store
-     *
-     * @param homeDir the home directory of the repository
-     * @throws RepositoryException
-     */
-    void init(String homeDir) throws RepositoryException;
-
-    /**
-     * Get the minimum size of an object that should be stored in this data store.
-     * Depending on the overhead and configuration, each store may return a different value.
-     *
-     * @return the minimum size in bytes
-     */
-    int getMinRecordLength();
-
-    /**
-     * Close the data store
-     *
-     * @throws DataStoreException if a problem occurred
-     */
-    void close() throws DataStoreException;
-
-    /**
-     * Clear the in-use list. This is only used for testing to make the the garbage collection
-     * think that objects are no longer in use.
-     */
-    void clearInUse();
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/DataStoreFactory.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/DataStoreFactory.java
deleted file mode 100644
index bf1ea5e..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/DataStoreFactory.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.data;
-
-import javax.jcr.RepositoryException;
-
-/**
- * Factory interface for creating {@link DataStore} instances. Used
- * to decouple the repository internals from the repository configuration
- * mechanism.
- *
- * @since Jackrabbit 1.5
- * @see <a href="https://issues.apache.org/jira/browse/JCR-1438">JCR-1438</a>
- */
-public interface DataStoreFactory {
-
-    /**
-     * Creates, initializes, and returns a {@link DataStore} instance
-     * for use by the repository. Note that no information is passed from
-     * the client, so all required configuration information must be
-     * encapsulated in the factory.
-     *
-     * @return initialized data store
-     * @throws RepositoryException if the data store can not be created
-     */
-    DataStore getDataStore() throws RepositoryException;
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataRecord.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataRecord.java
deleted file mode 100644
index 70b2e76..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataRecord.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.data;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Data record that is based on a normal file.
- */
-public class FileDataRecord extends AbstractDataRecord {
-
-    /**
-     * The file that contains the binary stream.
-     */
-    private final File file;
-
-    /**
-     * Creates a data record based on the given identifier and file.
-     *
-     * @param identifier data identifier
-     * @param file file that contains the binary stream
-     */
-    public FileDataRecord(DataIdentifier identifier, File file) {
-        super(identifier);
-        assert file.isFile();
-        this.file = file;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public long getLength() {
-        return file.length();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public InputStream getStream() throws DataStoreException {
-        try {
-            return new LazyFileInputStream(file);
-        } catch (IOException e) {
-            throw new DataStoreException("Error opening input stream of " + file.getAbsolutePath(), e);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public long getLastModified() {
-        return file.lastModified();
-    }
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataStore.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataStore.java
deleted file mode 100644
index c878ecc..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/FileDataStore.java
+++ /dev/null
@@ -1,459 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.data;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.RandomAccessFile;
-import java.lang.ref.WeakReference;
-import java.security.DigestOutputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.sql.Timestamp;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.WeakHashMap;
-
-import org.apache.commons.io.IOUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Simple file-based data store. Data records are stored as normal files
- * named using a message digest of the contained binary stream.
- *
- * Configuration:
- * <pre>
- * <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
- *     <param name="{@link #setPath(String) path}" value="/data/datastore"/>
- *     <param name="{@link #setMinRecordLength(int) minRecordLength}" value="1024"/>
- * &lt/DataStore>
- * </pre>
- * <p>
- * If the directory is not set, the directory <repository home>/repository/datastore is used.
- * <p>
- * A three level directory structure is used to avoid placing too many
- * files in a single directory. The chosen structure is designed to scale
- * up to billions of distinct records.
- * <p>
- * This implementation relies on the underlying file system to support
- * atomic O(1) move operations with {@link File#renameTo(File)}.
- */
-public class FileDataStore implements DataStore {
-
-    /**
-     * Logger instance
-     */
-    private static Logger log = LoggerFactory.getLogger(FileDataStore.class);
-
-    /**
-     * The digest algorithm used to uniquely identify records.
-     */
-    private static final String DIGEST = "SHA-1";
-
-    /**
-     * The default value for the minimum object size.
-     */
-    private static final int DEFAULT_MIN_RECORD_LENGTH = 100;
-
-    /**
-     * The maximum last modified time resolution of the file system.
-     */
-    private static final int ACCESS_TIME_RESOLUTION = 2000;
-
-    /**
-     * Name of the directory used for temporary files.
-     * Must be at least 3 characters.
-     */
-    private static final String TMP = "tmp";
-
-    /**
-     * The minimum modified date. If a file is accessed (read or write) with a modified date
-     * older than this value, the modified date is updated to the current time.
-     */
-    private long minModifiedDate;
-
-    /**
-     * The directory that contains all the data record files. The structure
-     * of content within this directory is controlled by this class.
-     */
-    private File directory;
-
-    /**
-     * The name of the directory that contains all the data record files. The structure
-     * of content within this directory is controlled by this class.
-     */
-    private String path;
-
-    /**
-     * The minimum size of an object that should be stored in this data store.
-     */
-    private int minRecordLength = DEFAULT_MIN_RECORD_LENGTH;
-
-    /**
-     * All data identifiers that are currently in use are in this set until they are garbage collected.
-     */
-    protected Map<DataIdentifier, WeakReference<DataIdentifier>> inUse =
-        Collections.synchronizedMap(new WeakHashMap<DataIdentifier, WeakReference<DataIdentifier>>());
-
-    /**
-     * Initialized the data store.
-     * If the path is not set, <repository home>/repository/datastore is used.
-     * This directory is automatically created if it does not yet exist.
-     *
-     * @param homeDir
-     */
-    public void init(String homeDir) {
-        if (path == null) {
-            path = homeDir + "/repository/datastore";
-        }
-        directory = new File(path);
-        directory.mkdirs();
-    }
-
-    public DataRecord getRecordIfStored(DataIdentifier identifier) throws DataStoreException {
-        return getRecord(identifier, true);
-    }
-
-    /**
-     * Get a data record for the given identifier.
-     * This method only checks if the file exists if the verify flag is set.
-     * If the verify flag is set and the file doesn't exist, the method returns null.
-     *
-     * @param identifier the identifier
-     * @param verify whether to check if the file exists
-     * @return the data record or null
-     */
-    private DataRecord getRecord(DataIdentifier identifier, boolean verify) throws DataStoreException {
-        File file = getFile(identifier);
-        synchronized (this) {
-            if (verify && !file.exists()) {
-                return null;
-            }
-            if (minModifiedDate != 0) {
-                // only check when running garbage collection
-                if (getLastModified(file) < minModifiedDate) {
-                    setLastModified(file, System.currentTimeMillis() + ACCESS_TIME_RESOLUTION);
-                }
-            }
-            usesIdentifier(identifier);
-            return new FileDataRecord(identifier, file);
-        }
-    }
-
-    /**
-     * Returns the record with the given identifier. Note that this method
-     * performs no sanity checks on the given identifier. It is up to the
-     * caller to ensure that only identifiers of previously created data
-     * records are used.
-     *
-     * @param identifier data identifier
-     * @return identified data record
-     */
-    public DataRecord getRecord(DataIdentifier identifier) throws DataStoreException {
-        return getRecord(identifier, false);
-    }
-
-    private void usesIdentifier(DataIdentifier identifier) {
-        inUse.put(identifier, new WeakReference<DataIdentifier>(identifier));
-    }
-
-    /**
-     * Creates a new data record.
-     * The stream is first consumed and the contents are saved in a temporary file
-     * and the SHA-1 message digest of the stream is calculated. If a
-     * record with the same SHA-1 digest (and length) is found then it is
-     * returned. Otherwise the temporary file is moved in place to become
-     * the new data record that gets returned.
-     *
-     * @param input binary stream
-     * @return data record that contains the given stream
-     * @throws DataStoreException if the record could not be created
-     */
-    public DataRecord addRecord(InputStream input) throws DataStoreException {
-        File temporary = null;
-        try {
-            temporary = newTemporaryFile();
-            DataIdentifier tempId = new DataIdentifier(temporary.getName());
-            usesIdentifier(tempId);
-            // Copy the stream to the temporary file and calculate the
-            // stream length and the message digest of the stream
-            long length = 0;
-            MessageDigest digest = MessageDigest.getInstance(DIGEST);
-            OutputStream output = new DigestOutputStream(
-                    new FileOutputStream(temporary), digest);
-            try {
-                length = IOUtils.copyLarge(input, output);
-            } finally {
-                output.close();
-            }
-            DataIdentifier identifier = new DataIdentifier(digest.digest());
-            File file;
-
-            synchronized (this) {
-                // Check if the same record already exists, or
-                // move the temporary file in place if needed
-                usesIdentifier(identifier);
-                file = getFile(identifier);
-                if (!file.exists()) {
-                    File parent = file.getParentFile();
-                    parent.mkdirs();
-                    if (temporary.renameTo(file)) {
-                        // no longer need to delete the temporary file
-                        temporary = null;
-                    } else {
-                        throw new IOException(
-                                "Can not rename " + temporary.getAbsolutePath()
-                                + " to " + file.getAbsolutePath()
-                                + " (media read only?)");
-                    }
-                } else {
-                    long now = System.currentTimeMillis();
-                    if (getLastModified(file) < now + ACCESS_TIME_RESOLUTION) {
-                        setLastModified(file, now + ACCESS_TIME_RESOLUTION);
-                    }
-                }
-                if (file.length() != length) {
-                    // Sanity checks on the record file. These should never fail,
-                    // but better safe than sorry...
-                    if (!file.isFile()) {
-                        throw new IOException("Not a file: " + file);
-                    }
-                    throw new IOException(DIGEST + " collision: " + file);
-                }
-            }
-            // this will also make sure that
-            // tempId is not garbage collected until here
-            inUse.remove(tempId);
-            return new FileDataRecord(identifier, file);
-        } catch (NoSuchAlgorithmException e) {
-            throw new DataStoreException(DIGEST + " not available", e);
-        } catch (IOException e) {
-            throw new DataStoreException("Could not add record", e);
-        } finally {
-            if (temporary != null) {
-                temporary.delete();
-            }
-        }
-    }
-
-    /**
-     * Returns the identified file. This method implements the pattern
-     * used to avoid problems with too many files in a single directory.
-     * <p>
-     * No sanity checks are performed on the given identifier.
-     *
-     * @param identifier data identifier
-     * @return identified file
-     */
-    private File getFile(DataIdentifier identifier) {
-        usesIdentifier(identifier);
-        String string = identifier.toString();
-        File file = directory;
-        file = new File(file, string.substring(0, 2));
-        file = new File(file, string.substring(2, 4));
-        file = new File(file, string.substring(4, 6));
-        return new File(file, string);
-    }
-
-    /**
-     * Returns a unique temporary file to be used for creating a new
-     * data record.
-     *
-     * @return temporary file
-     * @throws IOException
-     */
-    private File newTemporaryFile() throws IOException {
-        // the directory is already created in the init method
-        return File.createTempFile(TMP, null, directory);
-    }
-
-    public void updateModifiedDateOnAccess(long before) {
-        minModifiedDate = before;
-    }
-
-    public int deleteAllOlderThan(long min) {
-        return deleteOlderRecursive(directory, min);
-    }
-
-    private int deleteOlderRecursive(File file, long min) {
-        int count = 0;
-        if (file.isFile() && file.exists() && file.canWrite()) {
-            synchronized (this) {
-                long lastModified;
-                try {
-                    lastModified = getLastModified(file);
-                } catch (DataStoreException e) {
-                    log.warn("Failed to read modification date; file not deleted", e);
-                    // don't delete the file, since the lastModified date is uncertain
-                    lastModified = min;
-                }
-                if (lastModified < min) {
-                    DataIdentifier id = new DataIdentifier(file.getName());
-                    if (!inUse.containsKey(id)) {
-                        if (log.isInfoEnabled()) {
-                            log.info("Deleting old file " + file.getAbsolutePath() +
-                                    " modified: " + new Timestamp(lastModified).toString() +
-                                    " length: " + file.length());
-                        }
-                        if (!file.delete()) {
-                            log.warn("Failed to delete old file " + file.getAbsolutePath());
-                        }
-                        count++;
-                    }
-                }
-            }
-        } else if (file.isDirectory()) {
-            File[] list = file.listFiles();
-            if (list != null) {
-                for (File f: list) {
-                    count += deleteOlderRecursive(f, min);
-                }
-            }
-
-            // JCR-1396: FileDataStore Garbage Collector and empty directories
-            // Automatic removal of empty directories (but not the root!)
-            synchronized (this) {
-                if (file != directory) {
-                    list = file.listFiles();
-                    if (list != null && list.length == 0) {
-                        file.delete();
-                    }
-                }
-            }
-        }
-        return count;
-    }
-
-    private void listRecursive(List<File> list, File file) {
-        File[] files = file.listFiles();
-        if (files != null) {
-            for (File f : files) {
-                if (f.isDirectory()) {
-                    listRecursive(list, f);
-                } else {
-                    list.add(f);
-                }
-            }
-        }
-    }
-
-    public Iterator<DataIdentifier> getAllIdentifiers() {
-        ArrayList<File> files = new ArrayList<File>();
-        listRecursive(files, directory);
-        ArrayList<DataIdentifier> identifiers = new ArrayList<DataIdentifier>();
-        for (File f: files) {
-            String name = f.getName();
-            if (!name.startsWith(TMP)) {
-                DataIdentifier id = new DataIdentifier(name);
-                identifiers.add(id);
-            }
-        }
-        return identifiers.iterator();
-    }
-
-    public void clearInUse() {
-        inUse.clear();
-    }
-
-    /**
-     * Get the name of the directory where this data store keeps the files.
-     *
-     * @return the full path name
-     */
-    public String getPath() {
-        return path;
-    }
-
-    /**
-     * Set the name of the directory where this data store keeps the files.
-     *
-     * @param directoryName the path name
-     */
-    public void setPath(String directoryName) {
-        this.path = directoryName;
-    }
-
-    public int getMinRecordLength() {
-        return minRecordLength;
-    }
-
-    /**
-     * Set the minimum object length.
-     *
-     * @param minRecordLength the length
-     */
-    public void setMinRecordLength(int minRecordLength) {
-        this.minRecordLength = minRecordLength;
-    }
-
-    public void close() {
-        // nothing to do
-    }
-
-    /**
-     * Get the last modified date of a file.
-     *
-     * @param file the file
-     * @return the last modified date
-     * @throws DataStoreException if reading fails
-     */
-    private static long getLastModified(File file) throws DataStoreException {
-        long lastModified = file.lastModified();
-        if (lastModified == 0) {
-            throw new DataStoreException("Failed to read record modified date: " + file.getAbsolutePath());
-        }
-        return lastModified;
-    }
-
-    /**
-     * Set the last modified date of a file, if the file is writable.
-     *
-     * @param file the file
-     * @param time the new last modified date
-     * @throws DataStoreException if the file is writable but modifying the date fails
-     */
-    private static void setLastModified(File file, long time) throws DataStoreException {
-        if (!file.setLastModified(time)) {
-            if (!file.canWrite()) {
-                // if we can't write to the file, so garbage collection will also not delete it
-                // (read only files or file systems)
-                return;
-            }
-            try {
-                // workaround for Windows: if the file is already open for reading
-                // (in this or another process), then setting the last modified date
-                // doesn't work - see also JCR-2872
-                RandomAccessFile r = new RandomAccessFile(file, "rw");
-                try {
-                    r.setLength(r.length());
-                } finally {
-                    r.close();
-                }
-            } catch (IOException e) {
-                throw new DataStoreException("An IO Exception occurred while trying to set the last modified date: " + file.getAbsolutePath(), e);
-            }
-        }
-    }
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/GarbageCollector.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/GarbageCollector.java
deleted file mode 100644
index 6d1dde3..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/GarbageCollector.java
+++ /dev/null
@@ -1,436 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.data;
-
-import org.apache.jackrabbit.api.management.DataStoreGarbageCollector;
-import org.apache.jackrabbit.api.management.MarkEventListener;
-import org.apache.jackrabbit.core.id.NodeId;
-import org.apache.jackrabbit.core.id.PropertyId;
-import org.apache.jackrabbit.core.observation.SynchronousEventListener;
-import org.apache.jackrabbit.core.persistence.IterablePersistenceManager;
-import org.apache.jackrabbit.core.state.ItemStateException;
-import org.apache.jackrabbit.core.state.NoSuchItemStateException;
-import org.apache.jackrabbit.core.state.NodeState;
-import org.apache.jackrabbit.core.state.PropertyState;
-import org.apache.jackrabbit.core.value.InternalValue;
-import org.apache.jackrabbit.spi.Name;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.ArrayList;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import javax.jcr.InvalidItemStateException;
-import javax.jcr.Item;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.PathNotFoundException;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.UnsupportedRepositoryOperationException;
-import javax.jcr.Workspace;
-import javax.jcr.observation.Event;
-import javax.jcr.observation.EventIterator;
-import javax.jcr.observation.ObservationManager;
-
-/**
- * Garbage collector for DataStore. This implementation is iterates through all
- * nodes and reads the binary properties. To detect nodes that are moved while
- * the scan runs, event listeners are started. Like the well known garbage
- * collection in Java, the items that are still in use are marked. Currently
- * this achieved by updating the modified date of the entries. Newly added
- * entries are detected because the modified date is changed when they are
- * added.
- * <p>
- * Example code to run the data store garbage collection:
- * <pre>
- * JackrabbitRepositoryFactory jf = (JackrabbitRepositoryFactory) factory;
- * RepositoryManager m = factory.getRepositoryManager((JackrabbitRepository) rep);
- * GarbageCollector gc = m.createDataStoreGarbageCollector();
- * try {
- *     gc.mark();
- *     gc.sweep();
- * } finally {
- *     gc.close();
- * }
- * </pre>
- */
-public class GarbageCollector implements DataStoreGarbageCollector {
-
-    /** logger instance */
-    private static final Logger LOG = LoggerFactory.getLogger(GarbageCollector.class);
-
-    private MarkEventListener callback;
-
-    private long sleepBetweenNodes;
-
-    protected int testDelay;
-
-    private final DataStore store;
-
-    private long startScanTimestamp;
-
-    private final ArrayList<Listener> listeners = new ArrayList<Listener>();
-
-    private final IterablePersistenceManager[] pmList;
-
-    private final Session[] sessionList;
-
-    private final AtomicBoolean closed = new AtomicBoolean();
-
-    private boolean persistenceManagerScan;
-
-    /**
-     * Create a new garbage collector.
-     * This method is usually not called by the application, it is called
-     * by SessionImpl.createDataStoreGarbageCollector().
-     *
-     * @param dataStore the data store to be garbage-collected
-     * @param list the persistence managers
-     * @param sessionList the sessions to access the workspaces
-     */
-    public GarbageCollector(
-            DataStore dataStore, IterablePersistenceManager[] list,
-            Session[] sessionList) {
-        this.store = dataStore;
-        this.pmList = list;
-        this.persistenceManagerScan = list != null;
-        this.sessionList = sessionList;
-    }
-
-    public void setSleepBetweenNodes(long millis) {
-        this.sleepBetweenNodes = millis;
-    }
-
-    public long getSleepBetweenNodes() {
-        return sleepBetweenNodes;
-    }
-
-    /**
-     * When testing the garbage collection, a delay is used instead of simulating concurrent access.
-     *
-     * @param testDelay the delay in milliseconds
-     */
-    public void setTestDelay(int testDelay) {
-        this.testDelay = testDelay;
-    }
-
-    /**
-     * @deprecated use setMarkEventListener().
-     */
-    public void setScanEventListener(ScanEventListener callback) {
-        setMarkEventListener(callback);
-    }
-
-    public void setMarkEventListener(MarkEventListener callback) {
-        this.callback = callback;
-    }
-
-    /**
-     * @deprecated use mark().
-     */
-    public void scan() throws RepositoryException {
-        mark();
-    }
-
-    public void mark() throws RepositoryException {
-        if (store == null) {
-            throw new RepositoryException("No DataStore configured.");
-        }
-        long now = System.currentTimeMillis();
-        if (startScanTimestamp == 0) {
-            startScanTimestamp = now;
-            store.updateModifiedDateOnAccess(startScanTimestamp);
-        }
-
-        if (pmList == null || !persistenceManagerScan) {
-            for (Session s : sessionList) {
-                scanNodes(s);
-            }
-        } else {
-            try {
-                scanPersistenceManagers();
-            } catch (ItemStateException e) {
-                throw new RepositoryException(e);
-            }
-        }
-    }
-
-    private void scanNodes(Session session) throws RepositoryException {
-
-        // add a listener to get 'new' nodes
-        // actually, new nodes are not the problem, but moved nodes
-        listeners.add(new Listener(session));
-
-        // adding a link to a BLOB updates the modified date
-        // reading usually doesn't, but when scanning, it does
-        recurse(session.getRootNode(), sleepBetweenNodes);
-    }
-
-    public void setPersistenceManagerScan(boolean allow) {
-        persistenceManagerScan = allow;
-    }
-
-    public boolean isPersistenceManagerScan() {
-        return persistenceManagerScan;
-    }
-
-    /**
-     * @deprecated use isPersistenceManagerScan().
-     */
-    public boolean getPersistenceManagerScan() {
-        return isPersistenceManagerScan();
-    }
-
-    private void scanPersistenceManagers() throws RepositoryException, ItemStateException {
-        for (IterablePersistenceManager pm : pmList) {
-            for (NodeId id : pm.getAllNodeIds(null, 0)) {
-                if (callback != null) {
-                    callback.beforeScanning(null);
-                }
-                try {
-                    NodeState state = pm.load(id);
-                    Set<Name> propertyNames = state.getPropertyNames();
-                    for (Name name : propertyNames) {
-                        PropertyId pid = new PropertyId(id, name);
-                        PropertyState ps = pm.load(pid);
-                        if (ps.getType() == PropertyType.BINARY) {
-                            for (InternalValue v : ps.getValues()) {
-                                // getLength will update the last modified date
-                                // if the persistence manager scan is running
-                                v.getLength();
-                            }
-                        }
-                    }
-                } catch (NoSuchItemStateException e) {
-                    // the node may have been deleted or moved in the meantime
-                    // ignore it
-                }
-            }
-        }
-    }
-
-    /**
-     * Stop the observation listener if any are installed.
-     */
-    public void stopScan() throws RepositoryException {
-        if (listeners.size() > 0) {
-            for (Listener listener : listeners) {
-                try {
-                    listener.stop();
-                } catch (Exception e) {
-                    throw new RepositoryException(e);
-                }
-            }
-            listeners.clear();
-        }
-    }
-
-    /**
-     * @deprecated use sweep().
-     */
-    public int deleteUnused() throws RepositoryException {
-        return sweep();
-    }
-
-    public int sweep() throws RepositoryException {
-        if (startScanTimestamp == 0) {
-            throw new RepositoryException("scan must be called first");
-        }
-        stopScan();
-        return store.deleteAllOlderThan(startScanTimestamp);
-    }
-
-    /**
-     * Get the data store if one is used.
-     *
-     * @return the data store, or null
-     */
-    public DataStore getDataStore() {
-        return store;
-    }
-
-    void recurse(final Node n, long sleep) throws RepositoryException {
-        if (sleep > 0) {
-            try {
-                Thread.sleep(sleep);
-            } catch (InterruptedException e) {
-                // ignore
-            }
-        }
-        if (callback != null) {
-            callback.beforeScanning(n);
-        }
-        try {
-            for (PropertyIterator it = n.getProperties(); it.hasNext();) {
-                Property p = it.nextProperty();
-                try {
-                    if (p.getType() == PropertyType.BINARY) {
-                        if (n.hasProperty("jcr:uuid")) {
-                            rememberNode(n.getProperty("jcr:uuid").getString());
-                        } else {
-                            rememberNode(n.getPath());
-                        }
-                        if (p.isMultiple()) {
-                            checkLengths(p.getLengths());
-                        } else {
-                        	checkLengths(p.getLength());
-                        }
-                    }
-                } catch (InvalidItemStateException e) {
-                    LOG.debug("Property removed concurrently - ignoring", e);
-                }
-            }
-        } catch (InvalidItemStateException e) {
-            LOG.debug("Node removed concurrently - ignoring", e);
-        }
-        try {
-            for (NodeIterator it = n.getNodes(); it.hasNext();) {
-                recurse(it.nextNode(), sleep);
-            }
-        } catch (InvalidItemStateException e) {
-            LOG.debug("Node removed concurrently - ignoring", e);
-        }
-    }
-
-    private void rememberNode(String path) {
-        // Do nothing at the moment
-        // TODO It may be possible to delete some items early
-        /*
-         * To delete files early in the garbage collection scan, we could do
-         * this:
-         *
-         * A) If garbage collection was run before, see if there a file with the
-         * list of UUIDs ('uuids.txt').
-         *
-         * B) If yes, and if the checksum is ok, read all those nodes first (if
-         * not so many). This updates the modified date of all old files that
-         * are still in use. Afterwards, delete all files with an older modified
-         * date than the last scan! Newer files, and files that are read have a
-         * newer modification date.
-         *
-         * C) Delete the 'uuids.txt' file (in any case).
-         *
-         * D) Iterate (recurse) through all nodes and properties like now. If a
-         * node has a binary property, store the UUID of the node in the file
-         * ('uuids.txt'). Also store the time when the scan started.
-         *
-         * E) Checksum and close the file.
-         *
-         * F) Like now, delete files with an older modification date than this
-         * scan.
-         *
-         * We can't use node path for this, UUIDs are required as nodes could be
-         * moved around.
-         *
-         * This mechanism requires that all data stores update the last modified
-         * date when calling addRecord and that record already exists.
-         *
-         */
-    }
-
-    private void checkLengths(long... lengths) throws RepositoryException {
-        for (long length : lengths) {
-            if (length == -1) {
-                throw new RepositoryException("mark failed to access a property");
-            }
-        }
-    }
-
-    public void close() {
-        if (!closed.getAndSet(true)) {
-            try {
-                stopScan();
-            } catch (RepositoryException e) {
-                LOG.warn("An error occured when stopping the event listener", e);
-            }
-            for (Session s : sessionList) {
-                s.logout();
-            }
-        }
-    }
-
-    /**
-     * Auto-close in case the application didn't call it explicitly.
-     */
-    protected void finalize() throws Throwable {
-        close();
-        super.finalize();
-    }
-
-    /**
-     * Event listener to detect moved nodes.
-     * A SynchronousEventListener is used to make sure this method is called before the main iteration ends.
-     */
-    class Listener implements SynchronousEventListener {
-
-        private final Session session;
-
-        private final ObservationManager manager;
-
-        private Exception lastException;
-
-        Listener(Session session)
-                throws UnsupportedRepositoryOperationException,
-                RepositoryException {
-            this.session = session;
-            Workspace ws = session.getWorkspace();
-            manager = ws.getObservationManager();
-            manager.addEventListener(this, Event.NODE_ADDED, "/", true, null,
-                    null, false);
-        }
-
-        void stop() throws Exception {
-            if (lastException != null) {
-                throw lastException;
-            }
-            manager.removeEventListener(this);
-        }
-
-        public void onEvent(EventIterator events) {
-            if (testDelay > 0) {
-                try {
-                    Thread.sleep(testDelay);
-                } catch (InterruptedException e) {
-                    // ignore
-                }
-            }
-            while (events.hasNext()) {
-                Event event = events.nextEvent();
-                try {
-                    String path = event.getPath();
-                    try {
-                        Item item = session.getItem(path);
-                        if (item.isNode()) {
-                            Node n = (Node) item;
-                            recurse(n, testDelay);
-                        }
-                    } catch (PathNotFoundException e) {
-                        // ignore
-                    }
-                } catch (Exception e) {
-                    lastException = e;
-                }
-            }
-        }
-    }
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/LazyFileInputStream.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/LazyFileInputStream.java
deleted file mode 100644
index 2741966..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/LazyFileInputStream.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.data;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-
-import org.apache.commons.io.input.AutoCloseInputStream;
-
-/**
- * This input stream delays opening the file until the first byte is read, and
- * closes and discards the underlying stream as soon as the end of input has
- * been reached or when the stream is explicitly closed.
- */
-public class LazyFileInputStream extends AutoCloseInputStream {
-
-    /**
-     * The file descriptor to use.
-     */
-    protected final FileDescriptor fd;
-
-    /**
-     * The file to read from.
-     */
-    protected final File file;
-
-    /**
-     * True if the input stream was opened. It is also set to true if the stream
-     * was closed without reading (to avoid opening the file after the stream
-     * was closed).
-     */
-    protected boolean opened;
-
-    /**
-     * Creates a new <code>LazyFileInputStream</code> for the given file. If the
-     * file is unreadable, a FileNotFoundException is thrown.
-     * The file is not opened until the first byte is read from the stream.
-     *
-     * @param file the file
-     * @throws java.io.FileNotFoundException
-     */
-    public LazyFileInputStream(File file)
-            throws FileNotFoundException {
-        super(null);
-        if (!file.canRead()) {
-            throw new FileNotFoundException(file.getPath());
-        }
-        this.file = file;
-        this.fd = null;
-    }
-
-    /**
-     * Creates a new <code>LazyFileInputStream</code> for the given file
-     * descriptor.
-     * The file is not opened until the first byte is read from the stream.
-     *
-     * @param fdObj
-     */
-    public LazyFileInputStream(FileDescriptor fd) {
-        super(null);
-        this.file = null;
-        this.fd = fd;
-    }
-
-    /**
-     * Creates a new <code>LazyFileInputStream</code> for the given file. If the
-     * file is unreadable, a FileNotFoundException is thrown.
-     *
-     * @param name
-     * @throws java.io.FileNotFoundException
-     */
-    public LazyFileInputStream(String name) throws FileNotFoundException {
-        this(new File(name));
-    }
-
-    /**
-     * Open the stream if required.
-     *
-     * @throws java.io.IOException
-     */
-    protected void open() throws IOException {
-        if (!opened) {
-            opened = true;
-            if (fd != null) {
-                in = new FileInputStream(fd);
-            } else {
-                in = new FileInputStream(file);
-            }
-        }
-    }
-
-    public int read() throws IOException {
-        open();
-        return super.read();
-    }
-
-    public int available() throws IOException {
-        open();
-        return super.available();
-    }
-
-    public void close() throws IOException {
-        // make sure the file is not opened afterwards
-        opened = true;
-        
-        // only close the file if it was in fact opened
-        if (in != null) {
-            super.close();
-        }
-    }
-
-    public synchronized void reset() throws IOException {
-        open();
-        super.reset();
-    }
-
-    public boolean markSupported() {
-        try {
-            open();
-        } catch (IOException e) {
-            throw new IllegalStateException(e.toString());
-        }
-        return super.markSupported();
-    }
-
-    public synchronized void mark(int readlimit) {
-        try {
-            open();
-        } catch (IOException e) {
-            throw new IllegalStateException(e.toString());
-        }
-        super.mark(readlimit);
-    }
-
-    public long skip(long n) throws IOException {
-        open();
-        return super.skip(n);
-    }
-
-    public int read(byte[] b) throws IOException {
-        open();
-        return super.read(b, 0, b.length);
-    }
-
-    public int read(byte[] b, int off, int len) throws IOException {
-        open();
-        return super.read(b, off, len);
-    }
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataRecord.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataRecord.java
deleted file mode 100644
index d02cdf2..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataRecord.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.data.db;
-
-import org.apache.jackrabbit.core.data.AbstractDataRecord;
-import org.apache.jackrabbit.core.data.DataIdentifier;
-import org.apache.jackrabbit.core.data.DataStoreException;
-
-import java.io.BufferedInputStream;
-import java.io.InputStream;
-
-/**
- * Data record that is stored in a database
- */
-public class DbDataRecord extends AbstractDataRecord {
-
-    protected final DbDataStore store;
-    protected final long length;
-    protected long lastModified;
-
-    /**
-     * Creates a data record based on the given identifier and length.
-     *
-     * @param identifier data identifier
-     * @param length the length
-     * @param lastModified
-     */
-    public DbDataRecord(DbDataStore store, DataIdentifier identifier, long length, long lastModified) {
-        super(identifier);
-        this.store = store;
-        this.length = length;
-        this.lastModified = lastModified;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public long getLength() throws DataStoreException {
-        lastModified = store.touch(getIdentifier(), lastModified);
-        return length;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public InputStream getStream() throws DataStoreException {
-        lastModified = store.touch(getIdentifier(), lastModified);
-        return new BufferedInputStream(new DbInputStream(store, getIdentifier()));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public long getLastModified() {
-        return lastModified;
-    }
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java
deleted file mode 100644
index cb55a75..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java
+++ /dev/null
@@ -1,993 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.data.db;
-
-import org.apache.commons.io.input.CountingInputStream;
-import org.apache.jackrabbit.core.data.DataIdentifier;
-import org.apache.jackrabbit.core.data.DataRecord;
-import org.apache.jackrabbit.core.data.DataStore;
-import org.apache.jackrabbit.core.data.DataStoreException;
-import org.apache.jackrabbit.core.util.db.CheckSchemaOperation;
-import org.apache.jackrabbit.core.util.db.ConnectionFactory;
-import org.apache.jackrabbit.core.util.db.ConnectionHelper;
-import org.apache.jackrabbit.core.util.db.DatabaseAware;
-import org.apache.jackrabbit.core.util.db.DbUtility;
-import org.apache.jackrabbit.core.util.db.StreamWrapper;
-import org.apache.jackrabbit.util.Text;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.ref.WeakReference;
-import java.security.DigestInputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.UUID;
-import java.util.WeakHashMap;
-
-import javax.jcr.RepositoryException;
-import javax.sql.DataSource;
-
-/**
- * A data store implementation that stores the records in a database using JDBC.
- *
- * Configuration:
- * <pre>
- * <DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
- *     <param name="{@link #setUrl(String) url}" value="jdbc:postgresql:test"/>
- *     <param name="{@link #setUser(String) user}" value="sa"/>
- *     <param name="{@link #setPassword(String) password}" value="sa"/>
- *     <param name="{@link #setDatabaseType(String) databaseType}" value="postgresql"/>
- *     <param name="{@link #setDriver(String) driver}" value="org.postgresql.Driver"/>
- *     <param name="{@link #setMinRecordLength(int) minRecordLength}" value="1024"/>
- *     <param name="{@link #setMaxConnections(int) maxConnections}" value="2"/>
- *     <param name="{@link #setCopyWhenReading(boolean) copyWhenReading}" value="true"/>
- *     <param name="{@link #setTablePrefix(String) tablePrefix}" value=""/>
- *     <param name="{@link #setSchemaObjectPrefix(String) schemaObjectPrefix}" value=""/>
- *     <param name="{@link #setSchemaCheckEnabled(String) schemaCheckEnabled}" value="true"/>
- * &lt/DataStore>
- * </pre>
- * <p>
- * Only URL, user name and password usually need to be set.
- * The remaining settings are generated using the database URL sub-protocol from the
- * database type resource file.
- * <p>
- * JNDI can be used to get the connection. In this case, use the javax.naming.InitialContext as the driver,
- * and the JNDI name as the URL. If the user and password are configured in the JNDI resource,
- * they should not be configured here. Example JNDI settings:
- * <pre>
- * <param name="driver" value="javax.naming.InitialContext" />
- * <param name="url" value="java:comp/env/jdbc/Test" />
- * </pre>
- * <p>
- * For Microsoft SQL Server 2005, there is a problem reading large BLOBs. You will need to use
- * the JDBC driver version 1.2 or newer, and append ;responseBuffering=adaptive to the database URL.
- * Don't append ;selectMethod=cursor, otherwise it can still run out of memory.
- * Example database URL: jdbc:sqlserver://localhost:4220;DatabaseName=test;responseBuffering=adaptive
- * <p>
- * By default, the data is copied to a temp file when reading, to avoid problems when reading multiple
- * blobs at the same time.
- * <p>
- * The tablePrefix can be used to specify a schema and / or catalog name:
- * <param name="tablePrefix" value="ds.">
- */
-public class DbDataStore implements DataStore, DatabaseAware {
-
-    /**
-     * The default value for the minimum object size.
-     */
-    public static final int DEFAULT_MIN_RECORD_LENGTH = 100;
-
-    /**
-     * Write to a temporary file to get the length (slow, but always works).
-     * This is the default setting.
-     */
-    public static final String STORE_TEMP_FILE = "tempFile";
-
-    /**
-     * Call PreparedStatement.setBinaryStream(..., -1)
-     */
-    public static final String STORE_SIZE_MINUS_ONE = "-1";
-
-    /**
-     * Call PreparedStatement.setBinaryStream(..., Integer.MAX_VALUE)
-     */
-    public static final String STORE_SIZE_MAX = "max";
-
-    /**
-     * The digest algorithm used to uniquely identify records.
-     */
-    protected static final String DIGEST = "SHA-1";
-
-    /**
-     * The prefix used for temporary objects.
-     */
-    protected static final String TEMP_PREFIX = "TEMP_";
-
-    /**
-     * Logger instance
-     */
-    private static Logger log = LoggerFactory.getLogger(DbDataStore.class);
-
-    /**
-     * The minimum modified date. If a file is accessed (read or write) with a modified date
-     * older than this value, the modified date is updated to the current time.
-     */
-    protected long minModifiedDate;
-
-    /**
-     * The database URL used.
-     */
-    protected String url;
-
-    /**
-     * The database driver.
-     */
-    protected String driver;
-
-    /**
-     * The user name.
-     */
-    protected String user;
-
-    /**
-     * The password
-     */
-    protected String password;
-
-    /**
-     * The database type used.
-     */
-    protected String databaseType;
-
-    /**
-     * The minimum size of an object that should be stored in this data store.
-     */
-    protected int minRecordLength = DEFAULT_MIN_RECORD_LENGTH;
-
-    /**
-     * The prefix for the datastore table, empty by default.
-     */
-    protected String tablePrefix = "";
-
-    /**
-     * The prefix of the table names. By default it is empty.
-     */
-    protected String schemaObjectPrefix = "";
-
-    /**
-     * Whether the schema check must be done during initialization.
-     */
-    private boolean schemaCheckEnabled = true;
-
-    /**
-     * The logical name of the DataSource to use.
-     */
-    protected String dataSourceName;
-
-    /**
-     * This is the property 'table'
-     * in the [databaseType].properties file, initialized with the default value.
-     */
-    protected String tableSQL = "DATASTORE";
-
-    /**
-     * This is the property 'createTable'
-     * in the [databaseType].properties file, initialized with the default value.
-     */
-    protected String createTableSQL =
-        "CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY, LENGTH BIGINT, LAST_MODIFIED BIGINT, DATA BLOB)";
-
-    /**
-     * This is the property 'insertTemp'
-     * in the [databaseType].properties file, initialized with the default value.
-     */
-    protected String insertTempSQL =
-        "INSERT INTO ${tablePrefix}${table} VALUES(?, 0, ?, NULL)";
-
-    /**
-     * This is the property 'updateData'
-     * in the [databaseType].properties file, initialized with the default value.
-     */
-    protected String updateDataSQL =
-        "UPDATE ${tablePrefix}${table} SET DATA=? WHERE ID=?";
-
-    /**
-     * This is the property 'updateLastModified'
-     * in the [databaseType].properties file, initialized with the default value.
-     */
-    protected String updateLastModifiedSQL =
-        "UPDATE ${tablePrefix}${table} SET LAST_MODIFIED=? WHERE ID=? AND LAST_MODIFIED<?";
-
-    /**
-     * This is the property 'update'
-     * in the [databaseType].properties file, initialized with the default value.
-     */
-    protected String updateSQL =
-        "UPDATE ${tablePrefix}${table} SET ID=?, LENGTH=?, LAST_MODIFIED=? " +
-        "WHERE ID=? AND LAST_MODIFIED=?";
-
-    /**
-     * This is the property 'delete'
-     * in the [databaseType].properties file, initialized with the default value.
-     */
-    protected String deleteSQL =
-        "DELETE FROM ${tablePrefix}${table} WHERE ID=?";
-
-    /**
-     * This is the property 'deleteOlder'
-     * in the [databaseType].properties file, initialized with the default value.
-     */
-    protected String deleteOlderSQL =
-        "DELETE FROM ${tablePrefix}${table} WHERE LAST_MODIFIED<?";
-
-    /**
-     * This is the property 'selectMeta'
-     * in the [databaseType].properties file, initialized with the default value.
-     */
-    protected String selectMetaSQL =
-        "SELECT LENGTH, LAST_MODIFIED FROM ${tablePrefix}${table} WHERE ID=?";
-
-    /**
-     * This is the property 'selectAll'
-     * in the [databaseType].properties file, initialized with the default value.
-     */
-    protected String selectAllSQL =
-        "SELECT ID FROM ${tablePrefix}${table}";
-
-    /**
-     * This is the property 'selectData'
-     * in the [databaseType].properties file, initialized with the default value.
-     */
-    protected String selectDataSQL =
-        "SELECT ID, DATA FROM ${tablePrefix}${table} WHERE ID=?";
-
-    /**
-     * The stream storing mechanism used.
-     */
-    protected String storeStream = STORE_TEMP_FILE;
-
-    /**
-     * Copy the stream to a temp file before returning it.
-     * Enabled by default to support concurrent reads.
-     */
-    protected boolean copyWhenReading = true;
-
-    /**
-     * All data identifiers that are currently in use are in this set until they are garbage collected.
-     */
-    protected Map<DataIdentifier, WeakReference<DataIdentifier>> inUse =
-        Collections.synchronizedMap(new WeakHashMap<DataIdentifier, WeakReference<DataIdentifier>>());
-
-    /**
-     * The temporary identifiers that are currently in use.
-     */
-    protected List<String> temporaryInUse = Collections.synchronizedList(new ArrayList<String>());
-
-    /**
-     * The {@link ConnectionHelper} set in the {@link #init(String)} method.
-     * */
-    protected ConnectionHelper conHelper;
-
-    /**
-     * The repositories {@link ConnectionFactory}.
-     */
-    private ConnectionFactory connectionFactory;
-
-    public void setConnectionFactory(ConnectionFactory connnectionFactory) {
-        this.connectionFactory = connnectionFactory;
-    }
-
-    public DataRecord addRecord(InputStream stream) throws DataStoreException {
-        InputStream fileInput = null;
-        String tempId = null;
-        ResultSet rs = null;
-        try {
-            long tempModified;
-            while (true) {
-                try {
-                    tempModified = System.currentTimeMillis();
-                    String id = UUID.randomUUID().toString();
-                    tempId = TEMP_PREFIX + id;
-                    temporaryInUse.add(tempId);
-                    // SELECT LENGTH, LAST_MODIFIED FROM DATASTORE WHERE ID=?
-                    rs = conHelper.query(selectMetaSQL, tempId);
-                    boolean hasNext = rs.next();
-                    rs.close();
-                    rs = null;
-                    if (hasNext) {
-                        // re-try in the very, very unlikely event that the row already exists
-                        continue;
-                    }
-                    // INSERT INTO DATASTORE VALUES(?, 0, ?, NULL)
-                    conHelper.exec(insertTempSQL, tempId, tempModified);
-                    break;
-                } catch (Exception e) {
-                    throw convert("Can not insert new record", e);
-                } finally {
-                    DbUtility.close(rs);
-                    // prevent that rs.close() is called again
-                    rs = null;
-                }
-            }
-            MessageDigest digest = getDigest();
-            DigestInputStream dIn = new DigestInputStream(stream, digest);
-            CountingInputStream in = new CountingInputStream(dIn);
-            StreamWrapper wrapper;
-            if (STORE_SIZE_MINUS_ONE.equals(storeStream)) {
-                wrapper = new StreamWrapper(in, -1);
-            } else if (STORE_SIZE_MAX.equals(storeStream)) {
-                wrapper = new StreamWrapper(in, Integer.MAX_VALUE);
-            } else if (STORE_TEMP_FILE.equals(storeStream)) {
-                File temp = moveToTempFile(in);
-                fileInput = new BufferedInputStream(new TempFileInputStream(temp));
-                long length = temp.length();
-                wrapper = new StreamWrapper(fileInput, length);
-            } else {
-                throw new DataStoreException("Unsupported stream store algorithm: " + storeStream);
-            }
-            // UPDATE DATASTORE SET DATA=? WHERE ID=?
-            conHelper.exec(updateDataSQL, wrapper, tempId);
-            long length = in.getByteCount();
-            DataIdentifier identifier = new DataIdentifier(digest.digest());
-            usesIdentifier(identifier);
-            String id = identifier.toString();
-            long newModified;
-            while (true) {
-                newModified = System.currentTimeMillis();
-                if (checkExisting(tempId, length, identifier)) {
-                    touch(identifier, newModified);
-                    conHelper.exec(deleteSQL, tempId);
-                    break;
-                }
-                try {
-                    // UPDATE DATASTORE SET ID=?, LENGTH=?, LAST_MODIFIED=?
-                    // WHERE ID=? AND LAST_MODIFIED=?
-                    int count = conHelper.update(updateSQL,
-                            id, length, newModified, tempId, tempModified);
-                    // If update count is 0, the last modified time of the
-                    // temporary row was changed - which means we need to
-                    // re-try using a new last modified date (a later one)
-                    // because we need to ensure the new last modified date
-                    // is _newer_ than the old (otherwise the garbage
-                    // collection could delete rows)
-                    if (count != 0) {
-                        // update was successful
-                        break;
-                    }
-                } catch (SQLException e) {
-                    // duplicate key (the row already exists) - repeat
-                    // we use exception handling for flow control here, which is bad,
-                    // but the alternative is to use UPDATE ... WHERE ... (SELECT ...)
-                    // which could cause a deadlock in some databases - also,
-                    // duplicate key will only occur if somebody else concurrently
-                    // added the same record (which is very unlikely)
-                }
-                // SELECT LENGTH, LAST_MODIFIED FROM DATASTORE WHERE ID=?
-                rs = conHelper.query(selectMetaSQL, tempId);
-                if (!rs.next()) {
-                    // the row was deleted, which is unexpected / not allowed
-                    String msg =
-                        DIGEST + " temporary entry deleted: " +
-                            " id=" + tempId + " length=" + length;
-                    log.error(msg);
-                    throw new DataStoreException(msg);
-                }
-                tempModified = rs.getLong(2);
-                DbUtility.close(rs);
-                rs = null;
-            }
-            usesIdentifier(identifier);
-            DbDataRecord record = new DbDataRecord(this, identifier, length, newModified);
-            return record;
-        } catch (Exception e) {
-            throw convert("Can not insert new record", e);
-        } finally {
-            if (tempId != null) {
-                temporaryInUse.remove(tempId);
-            }
-            DbUtility.close(rs);
-            if (fileInput != null) {
-                try {
-                    fileInput.close();
-                } catch (IOException e) {
-                    throw convert("Can not close temporary file", e);
-                }
-            }
-        }
-    }
-
-    /**
-     * Check if a row with this ID already exists.
-     *
-     * @return true if the row exists and the length matches
-     * @throw DataStoreException if a row exists, but the length is different
-     */
-    private boolean checkExisting(String tempId, long length, DataIdentifier identifier) throws DataStoreException, SQLException {
-        String id = identifier.toString();
-        // SELECT LENGTH, LAST_MODIFIED FROM DATASTORE WHERE ID=?
-        ResultSet rs = null;
-        try {
-            rs = conHelper.query(selectMetaSQL, id);
-            if (rs.next()) {
-                long oldLength = rs.getLong(1);
-                long lastModified = rs.getLong(2);
-                if (oldLength != length) {
-                    String msg =
-                        DIGEST + " collision: temp=" + tempId
-                        + " id=" + id + " length=" + length
-                        + " oldLength=" + oldLength;
-                    log.error(msg);
-                    throw new DataStoreException(msg);
-                }
-                touch(identifier, lastModified);
-                // row already exists
-                conHelper.exec(deleteSQL, tempId);
-                return true;
-            }
-        } finally {
-            DbUtility.close(rs);
-        }
-        return false;
-    }
-
-    /**
-     * Creates a temp file and copies the data there.
-     * The input stream is closed afterwards.
-     *
-     * @param in the input stream
-     * @return the file
-     * @throws IOException
-     */
-    private File moveToTempFile(InputStream in) throws IOException {
-        File temp = File.createTempFile("dbRecord", null);
-        TempFileInputStream.writeToFileAndClose(in, temp);
-        return temp;
-    }
-
-    public synchronized int deleteAllOlderThan(long min) throws DataStoreException {
-        try {
-            ArrayList<String> touch = new ArrayList<String>();
-            ArrayList<DataIdentifier> ids = new ArrayList<DataIdentifier>(inUse.keySet());
-            for (DataIdentifier identifier: ids) {
-                if (identifier != null) {
-                    touch.add(identifier.toString());
-                }
-            }
-            touch.addAll(temporaryInUse);
-            for (String key : touch) {
-                updateLastModifiedDate(key, 0);
-            }
-            // DELETE FROM DATASTORE WHERE LAST_MODIFIED<?
-            return conHelper.update(deleteOlderSQL, min);
-        } catch (Exception e) {
-            throw convert("Can not delete records", e);
-        }
-    }
-
-    public Iterator<DataIdentifier> getAllIdentifiers() throws DataStoreException {
-        ArrayList<DataIdentifier> list = new ArrayList<DataIdentifier>();
-        ResultSet rs = null;
-        try {
-            // SELECT ID FROM DATASTORE
-            rs = conHelper.query(selectAllSQL);
-            while (rs.next()) {
-                String id = rs.getString(1);
-                if (!id.startsWith(TEMP_PREFIX)) {
-                    DataIdentifier identifier = new DataIdentifier(id);
-                    list.add(identifier);
-                }
-            }
-            return list.iterator();
-        } catch (Exception e) {
-            throw convert("Can not read records", e);
-        } finally {
-            DbUtility.close(rs);
-        }
-    }
-
-    public int getMinRecordLength() {
-        return minRecordLength;
-    }
-
-    /**
-     * Set the minimum object length.
-     * The maximum value is around 32000.
-     *
-     * @param minRecordLength the length
-     */
-    public void setMinRecordLength(int minRecordLength) {
-        this.minRecordLength = minRecordLength;
-    }
-
-    public DataRecord getRecordIfStored(DataIdentifier identifier) throws DataStoreException {
-        usesIdentifier(identifier);
-        ResultSet rs = null;
-        try {
-            String id = identifier.toString();
-            // SELECT LENGTH, LAST_MODIFIED FROM DATASTORE WHERE ID = ?
-            rs = conHelper.query(selectMetaSQL, id);
-            if (!rs.next()) {
-                throw new DataStoreException("Record not found: " + identifier);
-            }
-            long length = rs.getLong(1);
-            long lastModified = rs.getLong(2);
-            touch(identifier, lastModified);
-            return new DbDataRecord(this, identifier, length, lastModified);
-        } catch (Exception e) {
-            throw convert("Can not read identifier " + identifier, e);
-        } finally {
-            DbUtility.close(rs);
-        }
-    }
-
-    public DataRecord getRecord(DataIdentifier identifier) throws DataStoreException {
-        DataRecord record = getRecordIfStored(identifier);
-        if (record == null) {
-            throw new DataStoreException("Record not found: " + identifier);
-        }
-        return record;
-    }
-
-    /**
-     * Open the input stream. This method sets those fields of the caller
-     * that need to be closed once the input stream is read.
-     *
-     * @param inputStream the database input stream object
-     * @param identifier data identifier
-     * @throws DataStoreException if the data store could not be accessed,
-     *          or if the given identifier is invalid
-     */
-    InputStream openStream(DbInputStream inputStream, DataIdentifier identifier) throws DataStoreException {
-        ResultSet rs = null;
-        try {
-            // SELECT ID, DATA FROM DATASTORE WHERE ID = ?
-            rs = conHelper.query(selectDataSQL, identifier.toString());
-            if (!rs.next()) {
-                throw new DataStoreException("Record not found: " + identifier);
-            }
-            InputStream stream = rs.getBinaryStream(2);
-            if (stream == null) {
-                stream = new ByteArrayInputStream(new byte[0]);
-                DbUtility.close(rs);
-            } else if (copyWhenReading) {
-                // If we copy while reading, create a temp file and close the stream
-                File temp = moveToTempFile(stream);
-                stream = new BufferedInputStream(new TempFileInputStream(temp));
-                DbUtility.close(rs);
-            } else {
-                stream = new BufferedInputStream(stream);
-                inputStream.setResultSet(rs);
-            }
-            return stream;
-        } catch (Exception e) {
-            DbUtility.close(rs);
-            throw convert("Retrieving database resource ", e);
-        }
-    }
-
-    public synchronized void init(String homeDir) throws DataStoreException {
-        try {
-            initDatabaseType();
-
-            conHelper = createConnectionHelper(getDataSource());
-
-            if (isSchemaCheckEnabled()) {
-                createCheckSchemaOperation().run();
-            }
-        } catch (Exception e) {
-            throw convert("Can not init data store, driver=" + driver + " url=" + url + " user=" + user +
-                    " schemaObjectPrefix=" + schemaObjectPrefix + " tableSQL=" + tableSQL + " createTableSQL=" + createTableSQL, e);
-        }
-    }
-
-    private DataSource getDataSource() throws Exception {
-        if (getDataSourceName() == null || "".equals(getDataSourceName())) {
-            return connectionFactory.getDataSource(getDriver(), getUrl(), getUser(), getPassword());
-        } else {
-            return connectionFactory.getDataSource(dataSourceName);
-        }
-    }
-
-    /**
-     * This method is called from the {@link #init(String)} method of this class and returns a
-     * {@link ConnectionHelper} instance which is assigned to the {@code conHelper} field. Subclasses may
-     * override it to return a specialized connection helper.
-     *
-     * @param dataSrc the {@link DataSource} of this persistence manager
-     * @return a {@link ConnectionHelper}
-     * @throws Exception on error
-     */
-    protected ConnectionHelper createConnectionHelper(DataSource dataSrc) throws Exception {
-        return new ConnectionHelper(dataSrc, false);
-    }
-
-    /**
-     * This method is called from {@link #init(String)} after the
-     * {@link #createConnectionHelper(DataSource)} method, and returns a default {@link CheckSchemaOperation}.
-     *
-     * @return a new {@link CheckSchemaOperation} instance
-     */
-    protected final CheckSchemaOperation createCheckSchemaOperation() {
-        String tableName = tablePrefix + schemaObjectPrefix + tableSQL;
-        return new CheckSchemaOperation(conHelper, new ByteArrayInputStream(createTableSQL.getBytes()), tableName);
-    }
-
-    protected void initDatabaseType() throws DataStoreException {
-        boolean failIfNotFound = false;
-        if (databaseType == null) {
-            if (dataSourceName != null) {
-                try {
-                    databaseType = connectionFactory.getDataBaseType(dataSourceName);
-                } catch (RepositoryException e) {
-                    throw new DataStoreException(e);
-                }
-            } else {
-                if (!url.startsWith("jdbc:")) {
-                    return;
-                }
-                int start = "jdbc:".length();
-                int end = url.indexOf(':', start);
-                databaseType = url.substring(start, end);
-            }
-        } else {
-            failIfNotFound = true;
-        }
-
-        InputStream in =
-            DbDataStore.class.getResourceAsStream(databaseType + ".properties");
-        if (in == null) {
-            if (failIfNotFound) {
-                String msg =
-                    "Configuration error: The resource '" + databaseType
-                    + ".properties' could not be found;"
-                    + " Please verify the databaseType property";
-                log.debug(msg);
-                throw new DataStoreException(msg);
-            } else {
-                return;
-            }
-        }
-        Properties prop = new Properties();
-        try {
-            try {
-                prop.load(in);
-            } finally {
-            in.close();
-            }
-        } catch (IOException e) {
-            String msg = "Configuration error: Could not read properties '" + databaseType + ".properties'";
-            log.debug(msg);
-            throw new DataStoreException(msg, e);
-        }
-        if (driver == null) {
-            driver = getProperty(prop, "driver", driver);
-        }
-        tableSQL = getProperty(prop, "table", tableSQL);
-        createTableSQL = getProperty(prop, "createTable", createTableSQL);
-        insertTempSQL = getProperty(prop, "insertTemp", insertTempSQL);
-        updateDataSQL = getProperty(prop, "updateData", updateDataSQL);
-        updateLastModifiedSQL = getProperty(prop, "updateLastModified", updateLastModifiedSQL);
-        updateSQL = getProperty(prop, "update", updateSQL);
-        deleteSQL = getProperty(prop, "delete", deleteSQL);
-        deleteOlderSQL = getProperty(prop, "deleteOlder", deleteOlderSQL);
-        selectMetaSQL = getProperty(prop, "selectMeta", selectMetaSQL);
-        selectAllSQL = getProperty(prop, "selectAll", selectAllSQL);
-        selectDataSQL = getProperty(prop, "selectData", selectDataSQL);
-        storeStream = getProperty(prop, "storeStream", storeStream);
-        if (!STORE_SIZE_MINUS_ONE.equals(storeStream)
-                && !STORE_TEMP_FILE.equals(storeStream)
-                && !STORE_SIZE_MAX.equals(storeStream)) {
-            String msg = "Unsupported Stream store mechanism: " + storeStream
-                    + " supported are: " + STORE_SIZE_MINUS_ONE + ", "
-                    + STORE_TEMP_FILE + ", " + STORE_SIZE_MAX;
-            log.debug(msg);
-            throw new DataStoreException(msg);
-        }
-    }
-
-    /**
-     * Get the expanded property value. The following placeholders are supported:
-     * ${table}: the table name (the default is DATASTORE) and
-     * ${tablePrefix}: tablePrefix plus schemaObjectPrefix as set in the configuration
-     *
-     * @param prop the properties object
-     * @param key the key
-     * @param defaultValue the default value
-     * @return the property value (placeholders are replaced)
-     */
-    protected String getProperty(Properties prop, String key, String defaultValue) {
-        String sql = prop.getProperty(key, defaultValue);
-        sql = Text.replace(sql, "${table}", tableSQL).trim();
-        sql = Text.replace(sql, "${tablePrefix}", tablePrefix + schemaObjectPrefix).trim();
-        return sql;
-    }
-
-    /**
-     * Convert an exception to a data store exception.
-     *
-     * @param cause the message
-     * @param e the root cause
-     * @return the data store exception
-     */
-    protected DataStoreException convert(String cause, Exception e) {
-        log.warn(cause, e);
-        if (e instanceof DataStoreException) {
-            return (DataStoreException) e;
-        } else {
-            return new DataStoreException(cause, e);
-        }
-    }
-
-    public void updateModifiedDateOnAccess(long before) {
-        log.debug("Update modifiedDate on access before " + before);
-        minModifiedDate = before;
-    }
-
-    /**
-     * Update the modified date of an entry if required.
-     *
-     * @param identifier the entry identifier
-     * @param lastModified the current last modified date
-     * @return the new modified date
-     */
-    long touch(DataIdentifier identifier, long lastModified) throws DataStoreException {
-        usesIdentifier(identifier);
-        return updateLastModifiedDate(identifier.toString(), lastModified);
-    }
-
-    private long updateLastModifiedDate(String key, long lastModified) throws DataStoreException {
-        if (lastModified < minModifiedDate) {
-            long now = System.currentTimeMillis();
-            try {
-                // UPDATE DATASTORE SET LAST_MODIFIED = ? WHERE ID = ? AND LAST_MODIFIED < ?
-                conHelper.update(updateLastModifiedSQL, now, key, now);
-                return now;
-            } catch (Exception e) {
-                throw convert("Can not update lastModified", e);
-            }
-        }
-        return lastModified;
-    }
-
-    /**
-     * Get the database type (if set).
-     * @return the database type
-     */
-    public String getDatabaseType() {
-        return databaseType;
-    }
-
-    /**
-     * Set the database type. By default the sub-protocol of the JDBC database URL is used if it is not set.
-     * It must match the resource file [databaseType].properties. Example: mysql.
-     *
-     * @param databaseType
-     */
-    public void setDatabaseType(String databaseType) {
-        this.databaseType = databaseType;
-    }
-
-    /**
-     * Get the database driver
-     *
-     * @return the driver
-     */
-    public String getDriver() {
-        return driver;
-    }
-
-    /**
-     * Set the database driver class name.
-     * If not set, the default driver class name for the database type is used,
-     * as set in the [databaseType].properties resource; key 'driver'.
-     *
-     * @param driver
-     */
-    public void setDriver(String driver) {
-        this.driver = driver;
-    }
-
-    /**
-     * Get the password.
-     *
-     * @return the password
-     */
-    public String getPassword() {
-        return password;
-    }
-
-    /**
-     * Set the password.
-     *
-     * @param password
-     */
-    public void setPassword(String password) {
-        this.password = password;
-    }
-
-    /**
-     * Get the database URL.
-     *
-     * @return the URL
-     */
-    public String getUrl() {
-        return url;
-    }
-
-    /**
-     * Set the database URL.
-     * Example: jdbc:postgresql:test
-     *
-     * @param url
-     */
-    public void setUrl(String url) {
-        this.url = url;
-    }
-
-    /**
-     * Get the user name.
-     *
-     * @return the user name
-     */
-    public String getUser() {
-        return user;
-    }
-
-    /**
-     * Set the user name.
-     *
-     * @param user
-     */
-    public void setUser(String user) {
-        this.user = user;
-    }
-
-    /**
-     * @return whether the schema check is enabled
-     */
-    public final boolean isSchemaCheckEnabled() {
-        return schemaCheckEnabled;
-    }
-
-    /**
-     * @param enabled set whether the schema check is enabled
-     */
-    public final void setSchemaCheckEnabled(boolean enabled) {
-        schemaCheckEnabled = enabled;
-    }
-
-    public synchronized void close() throws DataStoreException {
-        // nothing to do
-    }
-
-    protected void usesIdentifier(DataIdentifier identifier) {
-        inUse.put(identifier, new WeakReference<DataIdentifier>(identifier));
-    }
-
-    public void clearInUse() {
-        inUse.clear();
-    }
-
-    protected synchronized MessageDigest getDigest() throws DataStoreException {
-        try {
-            return MessageDigest.getInstance(DIGEST);
-        } catch (NoSuchAlgorithmException e) {
-            throw convert("No such algorithm: " + DIGEST, e);
-        }
-    }
-
-    /**
-     * Get the maximum number of concurrent connections.
-     *
-     * @deprecated
-     * @return the maximum number of connections.
-     */
-    public int getMaxConnections() {
-        return -1;
-    }
-
-    /**
-     * Set the maximum number of concurrent connections in the pool.
-     * At least 3 connections are required if the garbage collection process is used.
-     *
-     *@deprecated
-     * @param maxConnections the new value
-     */
-    public void setMaxConnections(int maxConnections) {
-        // no effect
-    }
-
-    /**
-     * Is a stream copied to a temporary file before returning?
-     *
-     * @return the setting
-     */
-    public boolean getCopyWhenReading() {
-        return copyWhenReading;
-    }
-
-    /**
-     * The the copy setting. If enabled,
-     * a stream is always copied to a temporary file when reading a stream.
-     *
-     * @param copyWhenReading the new setting
-     */
-    public void setCopyWhenReading(boolean copyWhenReading) {
-        this.copyWhenReading = copyWhenReading;
-    }
-
-    /**
-     * Get the table prefix.
-     *
-     * @return the table prefix.
-     */
-    public String getTablePrefix() {
-        return tablePrefix;
-    }
-
-    /**
-     * Set the new table prefix. The default is empty.
-     * The table name is constructed like this:
-     * ${tablePrefix}${schemaObjectPrefix}${tableName}
-     *
-     * @param tablePrefix the new value
-     */
-    public void setTablePrefix(String tablePrefix) {
-        this.tablePrefix = tablePrefix;
-    }
-
-    /**
-     * Get the schema prefix.
-     *
-     * @return the schema object prefix
-     */
-    public String getSchemaObjectPrefix() {
-        return schemaObjectPrefix;
-    }
-
-    /**
-     * Set the schema object prefix. The default is empty.
-     * The table name is constructed like this:
-     * ${tablePrefix}${schemaObjectPrefix}${tableName}
-     *
-     * @param schemaObjectPrefix the new prefix
-     */
-    public void setSchemaObjectPrefix(String schemaObjectPrefix) {
-        this.schemaObjectPrefix = schemaObjectPrefix;
-    }
-
-    public String getDataSourceName() {
-        return dataSourceName;
-    }
-
-    public void setDataSourceName(String dataSourceName) {
-        this.dataSourceName = dataSourceName;
-    }
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/TempFileInputStream.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/TempFileInputStream.java
deleted file mode 100644
index 0d03a4a..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/TempFileInputStream.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.data.db;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.io.input.AutoCloseInputStream;
-
-/**
- * An input stream from a temporary file. The file is deleted when the stream is
- * closed, fully read, or garbage collected.
- * <p>
- * This class does not support mark/reset. It is always to be wrapped
- * using a BufferedInputStream.
- */
-public class TempFileInputStream extends AutoCloseInputStream {
-
-    private final File file;
-    private boolean closed;
-
-    /**
-     * Copy the data to a file and close the input stream afterwards.
-     *
-     * @param in the input stream
-     * @param file the target file
-     * @return the size of the file
-     */
-    public static long writeToFileAndClose(InputStream in, File file) throws IOException {
-        OutputStream out = new FileOutputStream(file);
-        IOUtils.copy(in, out);
-        out.close();
-        in.close();
-        return file.length();
-    }
-
-    /**
-     * Construct a new temporary file input stream.
-     * The file is deleted if the input stream is closed or fully read.
-     * Deleting is only attempted once.
-     *
-     * @param file the temporary file
-     */
-    public TempFileInputStream(File file) throws FileNotFoundException {
-        super(new BufferedInputStream(new FileInputStream(file)));
-        this.file = file;
-    }
-
-    private int closeIfEOF(int read) throws IOException {
-        if (read < 0) {
-            close();
-        }
-        return read;
-    }
-
-    public void close() throws IOException {
-        if (!closed) {
-            in.close();
-            file.delete();
-            closed = true;
-        }
-    }
-
-    public int available() throws IOException {
-        return in.available();
-    }
-
-    /**
-     * This method does nothing.
-     */
-    public void mark(int readlimit) {
-        // do nothing
-    }
-
-    /**
-     * Check whether mark and reset are supported.
-     *
-     * @return false
-     */
-    public boolean markSupported() {
-        return false;
-    }
-
-    public long skip(long n) throws IOException {
-        return in.skip(n);
-    }
-
-    public void reset() throws IOException {
-        in.reset();
-    }
-
-    public int read(byte[] b, int off, int len) throws IOException {
-        if (closed) {
-            return -1;
-        }
-        return closeIfEOF(in.read(b, off, len));
-    }
-
-    public int read(byte[] b) throws IOException {
-        if (closed) {
-            return -1;
-        }
-        return closeIfEOF(in.read(b));
-    }
-
-    public int read() throws IOException {
-        if (closed) {
-            return -1;
-        }
-        return closeIfEOF(in.read());
-    }
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/BasedFileSystem.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/BasedFileSystem.java
deleted file mode 100644
index 7d2ceb0..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/BasedFileSystem.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.fs;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * A <code>BasedFileSystem</code> represents a 'file system in a file system'.
- */
-public class BasedFileSystem implements FileSystem {
-
-    protected final FileSystem fsBase;
-
-    protected final String basePath;
-
-    /**
-     * Creates a new <code>BasedFileSystem</code>
-     *
-     * @param fsBase      the <code>FileSystem</code> the new file system should be based on
-     * @param relRootPath the root path relative to <code>fsBase</code>'s root
-     */
-    public BasedFileSystem(FileSystem fsBase, String relRootPath) {
-        if (fsBase == null) {
-            throw new IllegalArgumentException("invalid file system argument");
-        }
-        this.fsBase = fsBase;
-
-        if (relRootPath == null) {
-            throw new IllegalArgumentException("invalid null path argument");
-        }
-        if (relRootPath.equals(SEPARATOR)) {
-            throw new IllegalArgumentException("invalid path argument");
-        }
-        if (!relRootPath.startsWith(SEPARATOR)) {
-            relRootPath = SEPARATOR + relRootPath;
-        }
-        if (relRootPath.endsWith(SEPARATOR)) {
-            relRootPath = relRootPath.substring(0, relRootPath.length() - 1);
-
-        }
-        this.basePath = relRootPath;
-    }
-
-    protected String buildBasePath(String path) {
-        if (path.startsWith(SEPARATOR)) {
-            if (path.length() == 1) {
-                return basePath;
-            } else {
-                return basePath + path;
-            }
-        } else {
-            return basePath + SEPARATOR + path;
-        }
-    }
-
-    //-----------------------------------------------------------< FileSystem >
-    /**
-     * {@inheritDoc}
-     */
-    public void init() throws FileSystemException {
-        // check base path
-        if (!fsBase.isFolder(basePath)) {
-            fsBase.createFolder(basePath);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void close() throws FileSystemException {
-        // do nothing; base file system should be closed explicitly
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void createFolder(String folderPath) throws FileSystemException {
-        fsBase.createFolder(buildBasePath(folderPath));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void deleteFile(String filePath) throws FileSystemException {
-        fsBase.deleteFile(buildBasePath(filePath));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void deleteFolder(String folderPath) throws FileSystemException {
-        fsBase.deleteFolder(buildBasePath(folderPath));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public boolean exists(String path) throws FileSystemException {
-        return fsBase.exists(buildBasePath(path));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public InputStream getInputStream(String filePath) throws FileSystemException {
-        return fsBase.getInputStream(buildBasePath(filePath));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public OutputStream getOutputStream(String filePath) throws FileSystemException {
-        return fsBase.getOutputStream(buildBasePath(filePath));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public boolean hasChildren(String path) throws FileSystemException {
-        return fsBase.hasChildren(buildBasePath(path));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public boolean isFile(String path) throws FileSystemException {
-        return fsBase.isFile(buildBasePath(path));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public boolean isFolder(String path) throws FileSystemException {
-        return fsBase.isFolder(buildBasePath(path));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public long lastModified(String path) throws FileSystemException {
-        return fsBase.lastModified(buildBasePath(path));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public long length(String filePath) throws FileSystemException {
-        return fsBase.length(buildBasePath(filePath));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public String[] list(String folderPath) throws FileSystemException {
-        return fsBase.list(buildBasePath(folderPath));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public String[] listFiles(String folderPath) throws FileSystemException {
-        return fsBase.listFiles(buildBasePath(folderPath));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public String[] listFolders(String folderPath) throws FileSystemException {
-        return fsBase.listFolders(buildBasePath(folderPath));
-    }
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/FileSystemFactory.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/FileSystemFactory.java
deleted file mode 100644
index 17e1125..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/FileSystemFactory.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.fs;
-
-import javax.jcr.RepositoryException;
-
-/**
- * Factory interface for creating {@link FileSystem} instances. Used
- * to decouple the repository internals from the repository configuration
- * mechanism.
- */
-public interface FileSystemFactory {
-
-    /**
-     * Creates, initializes, and returns a {@link FileSystem} instance
-     * for use by the repository. Note that no information is passed from
-     * the client, so all required configuration information must be
-     * encapsulated in the factory.
-     *
-     * @return initialized file system
-     * @throws RepositoryException if the file system can not be created
-     */
-    FileSystem getFileSystem() throws RepositoryException;
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/FileSystemPathUtil.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/FileSystemPathUtil.java
deleted file mode 100644
index 76c13e3..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/FileSystemPathUtil.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.fs;
-
-import java.io.ByteArrayOutputStream;
-import java.util.BitSet;
-
-/**
- * Utility class for handling paths in a file system.
- */
-public final class FileSystemPathUtil {
-
-    /**
-     * Array of lowercase hexadecimal characters used in creating hex escapes.
-     */
-    private static final char[] HEX_TABLE = "0123456789abcdef".toCharArray();
-
-    /**
-     * The escape character used to mark hex escape sequences.
-     */
-    private static final char ESCAPE_CHAR = '%';
-
-    /**
-     * The list of characters that are not encoded by the <code>escapeName(String)</code>
-     * and <code>unescape(String)</code> methods. They contains the characters
-     * which can safely be used in file names:
-     */
-    public static final BitSet SAFE_NAMECHARS;
-
-    /**
-     * The list of characters that are not encoded by the <code>escapePath(String)</code>
-     * and <code>unescape(String)</code> methods. They contains the characters
-     * which can safely be used in file paths:
-     */
-    public static final BitSet SAFE_PATHCHARS;
-
-    static {
-        // build list of valid name characters
-        SAFE_NAMECHARS = new BitSet(256);
-        int i;
-        for (i = 'a'; i <= 'z'; i++) {
-            SAFE_NAMECHARS.set(i);
-        }
-        for (i = 'A'; i <= 'Z'; i++) {
-            SAFE_NAMECHARS.set(i);
-        }
-        for (i = '0'; i <= '9'; i++) {
-            SAFE_NAMECHARS.set(i);
-        }
-        SAFE_NAMECHARS.set('-');
-        SAFE_NAMECHARS.set('_');
-        SAFE_NAMECHARS.set('.');
-
-        // build list of valid path characters (includes name characters)
-        SAFE_PATHCHARS = (BitSet) SAFE_NAMECHARS.clone();
-        SAFE_PATHCHARS.set(FileSystem.SEPARATOR_CHAR);
-    }
-
-    /**
-     * private constructor
-     */
-    private FileSystemPathUtil() {
-    }
-
-    /**
-     * Escapes the given string using URL encoding for all bytes not included
-     * in the given set of safe characters.
-     *
-     * @param s the string to escape
-     * @param safeChars set of safe characters (bytes)
-     * @return escaped string
-     */
-    private static String escape(String s, BitSet safeChars) {
-        byte[] bytes = s.getBytes();
-        StringBuilder out = new StringBuilder(bytes.length);
-        for (int i = 0; i < bytes.length; i++) {
-            int c = bytes[i] & 0xff;
-            if (safeChars.get(c) && c != ESCAPE_CHAR) {
-                out.append((char) c);
-            } else {
-                out.append(ESCAPE_CHAR);
-                out.append(HEX_TABLE[(c >> 4) & 0x0f]);
-                out.append(HEX_TABLE[(c) & 0x0f]);
-            }
-        }
-        return out.toString();
-    }
-
-    /**
-     * Encodes the specified <code>path</code>. Same as
-     * <code>{@link #escapeName(String)}</code> except that the separator
-     * character <b><code>/</code></b> is regarded as a legal path character
-     * that needs no escaping.
-     *
-     * @param path the path to encode.
-     * @return the escaped path
-     */
-    public static String escapePath(String path) {
-        return escape(path, SAFE_PATHCHARS);
-    }
-
-    /**
-     * Encodes the specified <code>name</code>. Same as
-     * <code>{@link #escapePath(String)}</code> except that the separator character
-     * <b><code>/</code></b> is regarded as an illegal character that needs
-     * escaping.
-     *
-     * @param name the name to encode.
-     * @return the escaped name
-     */
-    public static String escapeName(String name) {
-        return escape(name, SAFE_NAMECHARS);
-    }
-
-    /**
-     * Decodes the specified path/name.
-     *
-     * @param pathOrName the escaped path/name
-     * @return the unescaped path/name
-     */
-    public static String unescape(String pathOrName) {
-        ByteArrayOutputStream out = new ByteArrayOutputStream(pathOrName.length());
-        for (int i = 0; i < pathOrName.length(); i++) {
-            char c = pathOrName.charAt(i);
-            if (c == ESCAPE_CHAR) {
-                try {
-                    out.write(Integer.parseInt(pathOrName.substring(i + 1, i + 3), 16));
-                } catch (NumberFormatException e) {
-                    IllegalArgumentException iae = new IllegalArgumentException("Failed to unescape escape sequence");
-                    iae.initCause(e);
-                    throw iae;
-                }
-                i += 2;
-            } else {
-                out.write(c);
-            }
-        }
-        return new String(out.toByteArray());
-    }
-
-    /**
-     * Tests whether the specified path represents the root path, i.e. "/".
-     *
-     * @param path path to test
-     * @return true if the specified path represents the root path; false otherwise.
-     */
-    public static boolean denotesRoot(String path) {
-        return path.equals(FileSystem.SEPARATOR);
-    }
-
-    /**
-     * Checks if <code>path</code> is a valid path.
-     *
-     * @param path the path to be checked
-     * @throws FileSystemException If <code>path</code> is not a valid path
-     */
-    public static void checkFormat(String path) throws FileSystemException {
-        if (path == null) {
-            throw new FileSystemException("null path");
-        }
-
-        // path must be absolute, i.e. starting with '/'
-        if (!path.startsWith(FileSystem.SEPARATOR)) {
-            throw new FileSystemException("not an absolute path: " + path);
-        }
-
-        // trailing '/' is not allowed (except for root path)
-        if (path.endsWith(FileSystem.SEPARATOR) && path.length() > 1) {
-            throw new FileSystemException("malformed path: " + path);
-        }
-
-        String[] names = path.split(FileSystem.SEPARATOR);
-        for (int i = 1; i < names.length; i++) {
-            // name must not be empty
-            if (names[i].length() == 0) {
-                throw new FileSystemException("empty name: " + path);
-            }
-            // leading/trailing whitespace is not allowed
-            String trimmed = names[i].trim();
-            if (!trimmed.equals(names[i])) {
-                throw new FileSystemException("illegal leading or trailing whitespace in name: " + path);
-            }
-        }
-    }
-
-    /**
-     * Returns the parent directory of the specified <code>path</code>.
-     *
-     * @param path a file system path denoting a directory or a file.
-     * @return the parent directory.
-     */
-    public static String getParentDir(String path) {
-        int pos = path.lastIndexOf(FileSystem.SEPARATOR_CHAR);
-        if (pos > 0) {
-            return path.substring(0, pos);
-        }
-        return FileSystem.SEPARATOR;
-    }
-
-    /**
-     * Returns the name of the specified <code>path</code>.
-     *
-     * @param path a file system path denoting a directory or a file.
-     * @return the name.
-     */
-    public static String getName(String path) {
-        int pos = path.lastIndexOf(FileSystem.SEPARATOR_CHAR);
-        if (pos != -1) {
-            return path.substring(pos + 1);
-        }
-        return path;
-    }
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/FileSystemResource.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/FileSystemResource.java
deleted file mode 100644
index 77c8546..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/FileSystemResource.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.fs;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import org.apache.commons.io.IOUtils;
-
-/**
- * A <code>FileSystemResource</code> represents a resource (i.e. file) in a
- * <code>FileSystem</code>.
- */
-public class FileSystemResource {
-
-    protected final FileSystem fs;
-
-    protected final String path;
-
-    static {
-        // preload FileSystemPathUtil to prevent classloader issues during shutdown
-        FileSystemPathUtil.class.hashCode();
-    }
-
-    /**
-     * Creates a new <code>FileSystemResource</code>
-     *
-     * @param fs   the <code>FileSystem</code> where the resource is located
-     * @param path the path of the resource in the <code>FileSystem</code>
-     */
-    public FileSystemResource(FileSystem fs, String path) {
-        if (fs == null) {
-            throw new IllegalArgumentException("invalid file system argument");
-        }
-        this.fs = fs;
-
-        if (path == null) {
-            throw new IllegalArgumentException("invalid path argument");
-        }
-        this.path = path;
-    }
-
-    /**
-     * Returns the <code>FileSystem</code> where this resource is located.
-     *
-     * @return the <code>FileSystem</code> where this resource is located.
-     */
-    public FileSystem getFileSystem() {
-        return fs;
-    }
-
-    /**
-     * Returns the path of this resource.
-     *
-     * @return the path of this resource.
-     */
-    public String getPath() {
-        return path;
-    }
-
-    /**
-     * Returns the parent directory of this resource.
-     *
-     * @return the parent directory.
-     */
-    public String getParentDir() {
-        return FileSystemPathUtil.getParentDir(path);
-    }
-
-    /**
-     * Returns the name of this resource.
-     *
-     * @return the name.
-     */
-    public String getName() {
-        return FileSystemPathUtil.getName(path);
-    }
-
-    /**
-     * Creates the parent directory of this resource, including any necessary
-     * but nonexistent parent directories.
-     *
-     * @throws FileSystemException
-     */
-    public synchronized void makeParentDirs() throws FileSystemException {
-        String parentDir = getParentDir();
-        if (!fs.exists(parentDir)) {
-            fs.createFolder(parentDir);
-        }
-    }
-
-    /**
-     * Deletes this resource.
-     * Same as <code>{@link #delete(false)}</code>.
-     *
-     * @see FileSystem#deleteFile
-     */
-    public void delete() throws FileSystemException {
-        delete(false);
-    }
-
-    /**
-     * Deletes this resource.
-     *
-     * @param pruneEmptyParentDirs if <code>true</code>, empty parent folders will
-     *                             automatically be deleted
-     * @see FileSystem#deleteFile
-     */
-    public synchronized void delete(boolean pruneEmptyParentDirs) throws FileSystemException {
-        fs.deleteFile(path);
-        if (pruneEmptyParentDirs) {
-            // prune empty parent folders
-            String parentDir = FileSystemPathUtil.getParentDir(path);
-            while (!parentDir.equals(FileSystem.SEPARATOR)
-                    && fs.exists(parentDir)
-                    && !fs.hasChildren(parentDir)) {
-                fs.deleteFolder(parentDir);
-                parentDir = FileSystemPathUtil.getParentDir(parentDir);
-            }
-        }
-    }
-
-    /**
-     * @see FileSystem#exists
-     */
-    public boolean exists() throws FileSystemException {
-        return fs.exists(path);
-    }
-
-    /**
-     * @see FileSystem#getInputStream
-     */
-    public InputStream getInputStream() throws FileSystemException {
-        return fs.getInputStream(path);
-    }
-
-    /**
-     * Spools this resource to the given output stream.
-     *
-     * @param out output stream where to spool the resource
-     * @throws FileSystemException if the input stream for this resource could
-     *                             not be obtained
-     * @throws IOException         if an error occurs while while spooling
-     * @see FileSystem#getInputStream
-     */
-    public void spool(OutputStream out) throws FileSystemException, IOException {
-        InputStream in = fs.getInputStream(path);
-        try {
-            IOUtils.copy(in, out);
-        } finally {
-            IOUtils.closeQuietly(in);
-        }
-    }
-
-    /**
-     * @see FileSystem#getOutputStream
-     */
-    public OutputStream getOutputStream() throws FileSystemException {
-        return fs.getOutputStream(path);
-    }
-
-    /**
-     * @see FileSystem#lastModified
-     */
-    public long lastModified() throws FileSystemException {
-        return fs.lastModified(path);
-    }
-
-    /**
-     * @see FileSystem#length
-     */
-    public long length() throws FileSystemException {
-        return fs.length(path);
-    }
-
-    //-------------------------------------------< java.lang.Object overrides >
-    /**
-     * Returns the path string of this resource. This is just the
-     * string returned by the <code>{@link #getPath}</code> method.
-     *
-     * @return The path string of this resource
-     */
-    public String toString() {
-        return getPath();
-    }
-
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj instanceof FileSystemResource) {
-            FileSystemResource other = (FileSystemResource) obj;
-            return (path == null ? other.path == null : path.equals(other.path))
-                    && (fs == null ? other.fs == null : fs.equals(other.fs));
-        }
-        return false;
-    }
-
-    /**
-     * Returns zero to satisfy the Object equals/hashCode contract.
-     * This class is mutable and not meant to be used as a hash key.
-     *
-     * @return always zero
-     * @see Object#hashCode()
-     */
-    public int hashCode() {
-        return 0;
-    }
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/DB2FileSystem.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/DB2FileSystem.java
index 5b07423..c689503 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/DB2FileSystem.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/DB2FileSystem.java
@@ -20,7 +20,7 @@ package org.apache.jackrabbit.core.fs.db;
  * <code>DB2FileSystem</code> is a JDBC-based <code>FileSystem</code>
  * implementation for Jackrabbit that persists file system entries in a
  * DB2 database.
- * <p/>
+ * <p>
  * It is configured through the following properties:
  * <ul>
  * <li><code>driver</code>: the FQN name of the JDBC driver class
@@ -34,7 +34,7 @@ package org.apache.jackrabbit.core.fs.db;
  * <li><code>schemaObjectPrefix</code>: prefix to be prepended to schema objects</li>
  * </ul>
  * See also {@link DbFileSystem}.
- * <p/>
+ * <p>
  * The following is a fragment from a sample configuration:
  * <pre>
  *   <FileSystem class="org.apache.jackrabbit.core.fs.db.DB2FileSystem">
@@ -57,7 +57,7 @@ public class DB2FileSystem extends DbFileSystem {
     //-----------------------------------------< DatabaseFileSystem overrides >
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Since DB2 requires parameter markers within the select clause to be
      * explicitly typed using <code>cast(? as type_name)</code> some statements
      * had to be changed accordingly.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/DbFileSystem.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/DbFileSystem.java
index e0f7296..ceff487 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/DbFileSystem.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/DbFileSystem.java
@@ -25,7 +25,7 @@ import javax.sql.DataSource;
  * <code>DbFileSystem</code> is a generic JDBC-based <code>FileSystem</code>
  * implementation for Jackrabbit that persists file system entries in a
  * database table.
- * <p/>
+ * <p>
  * It is configured through the following properties:
  * <ul>
  * <li><code>driver</code>: the FQN name of the JDBC driver class</li>
@@ -43,7 +43,7 @@ import javax.sql.DataSource;
  * <code>java.sql.Statement.execute(String)</code> where every occurence of the
  * the string <code>"${schemaObjectPrefix}"</code> has been replaced with the
  * value of the property <code>schemaObjectPrefix</code>.
- * <p/>
+ * <p>
  * The following is a fragment from a sample configuration using MySQL:
  * <pre>
  *   <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/DerbyFileSystem.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/DerbyFileSystem.java
index 2e4b1e5..8a9f66c 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/DerbyFileSystem.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/DerbyFileSystem.java
@@ -27,7 +27,7 @@ import javax.sql.DataSource;
  * <code>DerbyFileSystem</code> is a JDBC-based <code>FileSystem</code>
  * implementation for Jackrabbit that persists file system entries in an
  * embedded Derby database.
- * <p/>
+ * <p>
  * It is configured through the following properties:
  * <ul>
  * <li><code>url</code>: the database url of the form
@@ -44,7 +44,7 @@ import javax.sql.DataSource;
  * set this to <code>false</code> when using a standalone database</li>
  * </ul>
  * See also {@link DbFileSystem}.
- * <p/>
+ * <p>
  * The following is a fragment from a sample configuration:
  * <pre>
  *   <FileSystem class="org.apache.jackrabbit.core.fs.db.DerbyFileSystem">
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/MSSqlFileSystem.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/MSSqlFileSystem.java
index 05991eb..d3d3187 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/MSSqlFileSystem.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/MSSqlFileSystem.java
@@ -22,7 +22,7 @@ import org.apache.jackrabbit.core.util.db.CheckSchemaOperation;
  * <code>MSSqlFileSystem</code> is a JDBC-based <code>FileSystem</code>
  * implementation for Jackrabbit that persists file system entries in an
  * MS SQL database.
- * <p/>
+ * <p>
  * It is configured through the following properties:
  * <ul>
  * <li><code>driver</code>: the FQN name of the JDBC driver class
@@ -37,7 +37,7 @@ import org.apache.jackrabbit.core.util.db.CheckSchemaOperation;
  * <li><code>tableSpace</code>: the tablespace to use</li>
  * </ul>
  * See also {@link DbFileSystem}.
- * <p/>
+ * <p>
  * The following is a fragment from a sample configuration:
  * <pre>
  *   <FileSystem class="org.apache.jackrabbit.core.fs.db.MSSqlFileSystem">
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/OracleFileSystem.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/OracleFileSystem.java
index 1de184b..029c07a 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/OracleFileSystem.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/db/OracleFileSystem.java
@@ -26,7 +26,7 @@ import org.apache.jackrabbit.core.util.db.OracleConnectionHelper;
  * <code>OracleFileSystem</code> is a JDBC-based <code>FileSystem</code>
  * implementation for Jackrabbit that persists file system entries in an
  * Oracle database.
- * <p/>
+ * <p>
  * It is configured through the following properties:
  * <ul>
  * <li><code>driver</code>: the FQN name of the JDBC driver class
@@ -42,7 +42,7 @@ import org.apache.jackrabbit.core.util.db.OracleConnectionHelper;
  * <li><code>indexTablespace</code>: the tablespace to use for indexes</li>
  * </ul>
  * See also {@link DbFileSystem}.
- * <p/>
+ * <p>
  * The following is a fragment from a sample configuration:
  * <pre>
  *   <FileSystem class="org.apache.jackrabbit.core.fs.db.OracleFileSystem">
@@ -117,7 +117,7 @@ public class OracleFileSystem extends DbFileSystem {
     
     /**
      * Sets the Oracle tablespace for indexes.
-     * @param tablespace the Oracle tablespace for indexes.
+     * @param tablespaceName the Oracle tablespace for indexes.
      */
     public void setIndexTablespace(String tablespaceName) {
         this.indexTablespace = this.buildTablespaceClause(tablespaceName);
@@ -170,7 +170,7 @@ public class OracleFileSystem extends DbFileSystem {
     
     /**
      * Builds the SQL statements
-     * <p/>
+     * <p>
      * Since Oracle treats emtpy strings and BLOBs as null values the SQL
      * statements had to be adapated accordingly. The following changes were
      * necessary:
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/local/LocalFileSystem.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/local/LocalFileSystem.java
deleted file mode 100644
index f97624d..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/local/LocalFileSystem.java
+++ /dev/null
@@ -1,386 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.fs.local;
-
-import org.apache.jackrabbit.core.fs.FileSystem;
-import org.apache.jackrabbit.core.fs.FileSystemException;
-import org.apache.jackrabbit.util.LazyFileInputStream;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * A <code>LocalFileSystem</code> ...
- */
-public class LocalFileSystem implements FileSystem {
-
-    private static Logger log = LoggerFactory.getLogger(LocalFileSystem.class);
-
-    private File root;
-
-    private HandleMonitor monitor;
-
-    /**
-     * Default constructor
-     */
-    public LocalFileSystem() {
-    }
-
-    public String getPath() {
-        if (root != null) {
-            return root.getPath();
-        } else {
-            return null;
-        }
-    }
-
-    /**
-     * Sets the path to the root directory of this local filesystem. please note
-     * that this method can be called via reflection during initialization and
-     * must not be altered.
-     *
-     * @param rootPath the path to the root directory
-     */
-    public void setPath(String rootPath) {
-        setRoot(new File(osPath(rootPath)));
-    }
-
-    public void setRoot(File root) {
-        this.root = root;
-    }
-
-    /**
-     * Enables/Disables the use of the handle monitor.
-     *
-     * @param enable
-     */
-    public void setEnableHandleMonitor(String enable) {
-        setEnableHandleMonitor(Boolean.valueOf(enable).booleanValue());
-    }
-
-    /**
-     * Enables/Disables the use of the handle monitor.
-     *
-     * @param enable flag
-     */
-    public void setEnableHandleMonitor(boolean enable) {
-        if (enable && monitor == null) {
-            monitor = new HandleMonitor();
-        }
-        if (!enable && monitor != null) {
-            monitor = null;
-        }
-    }
-
-    /**
-     * Returns <code>true</code> if use of the handle monitor is currently
-     * enabled, otherwise returns <code>false</code>.
-     *
-     * @see #setEnableHandleMonitor(boolean)
-     */
-    public String getEnableHandleMonitor() {
-        return monitor == null ? "false" : "true";
-    }
-
-    private String osPath(String genericPath) {
-        if (File.separator.equals(SEPARATOR)) {
-            return genericPath;
-        }
-        return genericPath.replace(SEPARATOR_CHAR, File.separatorChar);
-    }
-
-    //-------------------------------------------< java.lang.Object overrides >
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (obj instanceof LocalFileSystem) {
-            LocalFileSystem other = (LocalFileSystem) obj;
-            if (root == null) {
-                return other.root == null;
-            } else {
-                return root.equals(other.root);
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Returns zero to satisfy the Object equals/hashCode contract.
-     * This class is mutable and not meant to be used as a hash key.
-     *
-     * @return always zero
-     * @see Object#hashCode()
-     */
-    public int hashCode() {
-        return 0;
-    }
-
-    //-----------------------------------------------------------< FileSystem >
-    /**
-     * {@inheritDoc}
-     */
-    public void init() throws FileSystemException {
-        if (root == null) {
-            String msg = "root directory not set";
-            log.debug(msg);
-            throw new FileSystemException(msg);
-        }
-
-        if (root.exists()) {
-            if (!root.isDirectory()) {
-                String msg = "path does not denote a folder";
-                log.debug(msg);
-                throw new FileSystemException(msg);
-            }
-        } else {
-            if (!root.mkdirs()) {
-                String msg = "failed to create root";
-                log.debug(msg);
-                throw new FileSystemException(msg);
-            }
-        }
-        log.info("LocalFileSystem initialized at path " + root.getPath());
-        if (monitor != null) {
-            log.info("LocalFileSystem using handle monitor");
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void close() throws FileSystemException {
-        root = null;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void createFolder(String folderPath) throws FileSystemException {
-        File f = new File(root, osPath(folderPath));
-        if (f.exists()) {
-            String msg = f.getPath() + " already exists";
-            log.debug(msg);
-            throw new FileSystemException(msg);
-        }
-        if (!f.mkdirs()) {
-            String msg = "failed to create folder " + f.getPath();
-            log.debug(msg);
-            throw new FileSystemException(msg);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void deleteFile(String filePath) throws FileSystemException {
-        File f = new File(root, osPath(filePath));
-        if (!f.isFile()) {
-            String msg = f.getPath() + " does not denote an existing file";
-            throw new FileSystemException(msg);
-        }
-        try {
-            FileUtil.delete(f);
-        } catch (IOException ioe) {
-            String msg = "failed to delete " + f.getPath();
-            if (monitor != null && monitor.isOpen(f)) {
-                log.error("Unable to delete. There are still open streams.");
-                monitor.dump(f);
-            }
-
-            throw new FileSystemException(msg, ioe);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void deleteFolder(String folderPath) throws FileSystemException {
-        File f = new File(root, osPath(folderPath));
-        if (!f.isDirectory()) {
-            String msg = f.getPath() + " does not denote an existing folder";
-            log.debug(msg);
-            throw new FileSystemException(msg);
-        }
-        try {
-            FileUtil.delete(f);
-        } catch (IOException ioe) {
-            String msg = "failed to delete " + f.getPath();
-            log.debug(msg);
-            throw new FileSystemException(msg, ioe);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public boolean exists(String path) throws FileSystemException {
-        File f = new File(root, osPath(path));
-        return f.exists();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public InputStream getInputStream(String filePath)
-            throws FileSystemException {
-        File f = new File(root, osPath(filePath));
-        try {
-            if (monitor == null) {
-                return new LazyFileInputStream(f);
-            } else {
-                return monitor.open(f);
-            }
-        } catch (FileNotFoundException fnfe) {
-            String msg = f.getPath() + " does not denote an existing file";
-            log.debug(msg);
-            throw new FileSystemException(msg, fnfe);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public OutputStream getOutputStream(String filePath)
-            throws FileSystemException {
-        File f = new File(root, osPath(filePath));
-        try {
-            return new FileOutputStream(f);
-        } catch (FileNotFoundException fnfe) {
-            String msg = "failed to get output stream for " + f.getPath();
-            log.debug(msg);
-            throw new FileSystemException(msg, fnfe);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public boolean hasChildren(String path) throws FileSystemException {
-        File f = new File(root, osPath(path));
-        if (!f.exists()) {
-            String msg = f.getPath() + " does not exist";
-            log.debug(msg);
-            throw new FileSystemException(msg);
-        }
-        if (f.isFile()) {
-            return false;
-        }
-        return (f.list().length > 0);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public boolean isFile(String path) throws FileSystemException {
-        File f = new File(root, osPath(path));
-        return f.isFile();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public boolean isFolder(String path) throws FileSystemException {
-        File f = new File(root, osPath(path));
-        return f.isDirectory();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public long lastModified(String path) throws FileSystemException {
-        File f = new File(root, osPath(path));
-        return f.lastModified();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public long length(String filePath) throws FileSystemException {
-        File f = new File(root, osPath(filePath));
-        if (!f.exists()) {
-            return -1;
-        }
-        return f.length();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public String[] list(String folderPath) throws FileSystemException {
-        File f = new File(root, osPath(folderPath));
-        String[] entries = f.list();
-        if (entries == null) {
-            String msg = folderPath + " does not denote a folder";
-            log.debug(msg);
-            throw new FileSystemException(msg);
-        }
-        return entries;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public String[] listFiles(String folderPath) throws FileSystemException {
-        File folder = new File(root, osPath(folderPath));
-        File[] files = folder.listFiles(new FileFilter() {
-            public boolean accept(File f) {
-                return f.isFile();
-            }
-        });
-        if (files == null) {
-            String msg = folderPath + " does not denote a folder";
-            log.debug(msg);
-            throw new FileSystemException(msg);
-        }
-        String[] entries = new String[files.length];
-        for (int i = 0; i < files.length; i++) {
-            entries[i] = files[i].getName();
-        }
-        return entries;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public String[] listFolders(String folderPath) throws FileSystemException {
-        File file = new File(root, osPath(folderPath));
-        File[] folders = file.listFiles(new FileFilter() {
-            public boolean accept(File f) {
-                return f.isDirectory();
-            }
-        });
-        if (folders == null) {
-            String msg = folderPath + " does not denote a folder";
-            log.debug(msg);
-            throw new FileSystemException(msg);
-        }
-        String[] entries = new String[folders.length];
-        for (int i = 0; i < folders.length; i++) {
-            entries[i] = folders[i].getName();
-        }
-        return entries;
-    }
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/gc/GarbageCollector.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/gc/GarbageCollector.java
new file mode 100644
index 0000000..ba574cf
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/gc/GarbageCollector.java
@@ -0,0 +1,511 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.gc;
+
+import org.apache.jackrabbit.api.management.DataStoreGarbageCollector;
+import org.apache.jackrabbit.api.management.MarkEventListener;
+import org.apache.jackrabbit.core.RepositoryContext;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.data.DataStore;
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.id.PropertyId;
+import org.apache.jackrabbit.core.observation.SynchronousEventListener;
+import org.apache.jackrabbit.core.persistence.IterablePersistenceManager;
+import org.apache.jackrabbit.core.persistence.util.NodeInfo;
+import org.apache.jackrabbit.core.state.ItemStateException;
+import org.apache.jackrabbit.core.state.NoSuchItemStateException;
+import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.core.state.PropertyState;
+import org.apache.jackrabbit.core.value.InternalValue;
+import org.apache.jackrabbit.spi.Name;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Workspace;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.ObservationManager;
+
+/**
+ * Garbage collector for DataStore. This implementation iterates through all
+ * nodes and reads the binary properties. To detect nodes that are moved while
+ * the scan runs, event listeners are started. Like the well known garbage
+ * collection in Java, the items that are still in use are marked. Currently
+ * this is achieved by updating the modified date of the entries. Newly added
+ * entries are detected because the modified date is changed when they are
+ * added.
+ * <p>
+ * Example code to run the data store garbage collection:
+ * <pre>
+ * JackrabbitRepositoryFactory jf = (JackrabbitRepositoryFactory) factory;
+ * RepositoryManager m = jf.getRepositoryManager((JackrabbitRepository) repository);
+ * GarbageCollector gc = m.createDataStoreGarbageCollector();
+ * try {
+ *     gc.mark();
+ *     gc.sweep();
+ * } finally {
+ *     gc.close();
+ * }
+ * </pre>
+ */
+public class GarbageCollector implements DataStoreGarbageCollector {
+
+    /** logger instance */
+    static final Logger LOG = LoggerFactory.getLogger(GarbageCollector.class);
+
+    /**
+     * The number of nodes to fetch at once from the persistence manager. Defaults to 8kb
+     */
+    private static final int NODESATONCE = Integer.getInteger("org.apache.jackrabbit.garbagecollector.nodesatonce", 1024 * 8);
+
+    /**
+     * Set this System Property to true to speed up the node traversing in a binary focused repository.
+     * See JCR-3708
+     */
+    private static final boolean NODE_ID_SCAN = Boolean.getBoolean("org.apache.jackrabbit.garbagecollector.node_id.scan");
+
+    private MarkEventListener callback;
+
+    private long sleepBetweenNodes;
+
+    protected int testDelay;
+
+    private final DataStore store;
+
+    private long startScanTimestamp;
+
+    private final ArrayList<Listener> listeners = new ArrayList<Listener>();
+
+    private final IterablePersistenceManager[] pmList;
+
+    private final SessionImpl[] sessionList;
+
+    private final AtomicBoolean closed = new AtomicBoolean();
+    
+    private final RepositoryContext context;
+
+    private boolean persistenceManagerScan;
+
+    private volatile RepositoryException observationException;
+
+    /**
+     * Create a new garbage collector.
+     * This method is usually not called by the application, it is called
+     * by SessionImpl.createDataStoreGarbageCollector().
+     * 
+     * @param context repository context
+     * @param dataStore the data store to be garbage-collected
+     * @param list the persistence managers
+     * @param sessionList the sessions to access the workspaces
+     */
+    
+    public GarbageCollector(RepositoryContext context, 
+            DataStore dataStore, IterablePersistenceManager[] list,
+            SessionImpl[] sessionList) {
+        this.context = context;
+        this.store = dataStore;
+        this.pmList = list;
+        this.persistenceManagerScan = list != null;
+        this.sessionList = sessionList;
+    }
+
+    public void setSleepBetweenNodes(long millis) {
+        this.sleepBetweenNodes = millis;
+    }
+
+    public long getSleepBetweenNodes() {
+        return sleepBetweenNodes;
+    }
+
+    /**
+     * When testing the garbage collection, a delay is used instead of simulating concurrent access.
+     *
+     * @param testDelay the delay in milliseconds
+     */
+    public void setTestDelay(int testDelay) {
+        this.testDelay = testDelay;
+    }
+
+    public void setMarkEventListener(MarkEventListener callback) {
+        this.callback = callback;
+    }
+
+    public void mark() throws RepositoryException {
+        if (store == null) {
+            throw new RepositoryException("No DataStore configured.");
+        }
+        long now = System.currentTimeMillis();
+        if (startScanTimestamp == 0) {
+            startScanTimestamp = now;
+            store.updateModifiedDateOnAccess(startScanTimestamp);
+        }
+
+        if (pmList == null || !persistenceManagerScan) {
+            for (SessionImpl s : sessionList) {
+                scanNodes(s);
+            }
+        } else {
+            try {
+                if (!NODE_ID_SCAN) {
+                    scanPersistenceManagersByNodeInfos();
+                } else {
+                    scanPersistenceManagersByNodeIds();
+                }
+            } catch (ItemStateException e) {
+                throw new RepositoryException(e);
+            }
+        }
+    }
+
+    private void scanNodes(SessionImpl session) throws RepositoryException {
+
+        // add a listener to get 'moved' nodes
+        Session clonedSession = session.createSession(session.getWorkspace().getName());
+        listeners.add(new Listener(this, clonedSession));
+
+        // adding a link to a BLOB updates the modified date
+        // reading usually doesn't, but when scanning, it does
+        recurse(session.getRootNode(), sleepBetweenNodes);
+    }
+
+    public void setPersistenceManagerScan(boolean allow) {
+        persistenceManagerScan = allow;
+    }
+
+    public boolean isPersistenceManagerScan() {
+        return persistenceManagerScan;
+    }
+
+    private void scanPersistenceManagersByNodeInfos() throws RepositoryException, ItemStateException {
+        int pmCount = 0;
+        for (IterablePersistenceManager pm : pmList) {
+            pmCount++;
+            int count = 0;
+            Map<NodeId,NodeInfo> batch = pm.getAllNodeInfos(null, NODESATONCE);
+            while (!batch.isEmpty()) {
+                NodeId lastId = null;
+                for (NodeInfo info : batch.values()) {
+                    count++;
+                    if (count % 1000 == 0) {
+                        LOG.debug(pm.toString() + " ("+pmCount + "/" + pmList.length + "): analyzed " + count + " nodes...");
+                    }
+                    lastId = info.getId();
+                    if (callback != null) {
+                        callback.beforeScanning(null);
+                    }
+                    if (info.hasBlobsInDataStore()) {
+                        try {
+                            NodeState state = pm.load(info.getId());
+                            Set<Name> propertyNames = state.getPropertyNames();
+                            for (Name name : propertyNames) {
+                                PropertyId pid = new PropertyId(info.getId(), name);
+                                PropertyState ps = pm.load(pid);
+                                if (ps.getType() == PropertyType.BINARY) {
+                                    for (InternalValue v : ps.getValues()) {
+                                        // getLength will update the last modified date
+                                        // if the persistence manager scan is running
+                                        v.getLength();
+                                    }
+                                }
+                            }
+                        } catch (NoSuchItemStateException ignored) {
+                            // the node may have been deleted in the meantime
+                        }
+                    }
+                }
+                batch = pm.getAllNodeInfos(lastId, NODESATONCE);
+            }
+        }
+        NodeInfo.clearPool();
+    }
+
+    private void scanPersistenceManagersByNodeIds() throws RepositoryException, ItemStateException {
+        int pmCount = 0;
+        for (IterablePersistenceManager pm : pmList) {
+            pmCount++;
+            List<NodeId> allNodeIds = pm.getAllNodeIds(null, 0);
+            int overAllCount = allNodeIds.size();
+            int count = 0;
+            for (NodeId id : allNodeIds) {
+                count++;
+                if (count % 1000 == 0) {
+                    LOG.debug(pm.toString() + " ("+pmCount + "/" + pmList.length + "): analyzed " + count + " nodes [" + overAllCount + "]...");
+                }
+                if (callback != null) {
+                    callback.beforeScanning(null);
+                }
+                try {
+                    NodeState state = pm.load(id);
+                    Set<Name> propertyNames = state.getPropertyNames();
+                    for (Name name : propertyNames) {
+                        PropertyId pid = new PropertyId(id, name);
+                        PropertyState ps = pm.load(pid);
+                        if (ps.getType() == PropertyType.BINARY) {
+                            for (InternalValue v : ps.getValues()) {
+                                // getLength will update the last modified date
+                                // if the persistence manager scan is running
+                                v.getLength();
+                            }
+                        }
+                    }
+                } catch (NoSuchItemStateException e) {
+                    // the node may have been deleted or moved in the meantime
+                    // ignore it
+                }
+            }
+        }
+    }
+
+    /**
+     * Reset modifiedDateOnAccess to 0 and stop the observation 
+     * listener if any are installed.
+     */
+    public void stopScan() throws RepositoryException {
+         // reset updateModifiedDateOnAccess to OL
+        store.updateModifiedDateOnAccess(0L);
+        
+        if (listeners.size() > 0) {
+            for (Listener listener : listeners) {
+                listener.stop();
+            }
+            listeners.clear();
+        }
+        checkObservationException();
+        context.setGcRunning(false);
+    }
+
+    public int sweep() throws RepositoryException {
+        if (startScanTimestamp == 0) {
+            throw new RepositoryException("scan must be called first");
+        }
+        stopScan();
+        return store.deleteAllOlderThan(startScanTimestamp);
+    }
+
+    /**
+     * Get the data store if one is used.
+     *
+     * @return the data store, or null
+     */
+    public DataStore getDataStore() {
+        return store;
+    }
+
+    void recurse(final Node n, long sleep) throws RepositoryException {
+        if (sleep > 0) {
+            try {
+                Thread.sleep(sleep);
+            } catch (InterruptedException e) {
+                // ignore
+            }
+        }
+        if (callback != null) {
+            callback.beforeScanning(n);
+        }
+        try {
+            for (PropertyIterator it = n.getProperties(); it.hasNext();) {
+                Property p = it.nextProperty();
+                try {
+                    if (p.getType() == PropertyType.BINARY) {
+                        if (n.hasProperty("jcr:uuid")) {
+                            rememberNode(n.getProperty("jcr:uuid").getString());
+                        } else {
+                            rememberNode(n.getPath());
+                        }
+                        if (p.isMultiple()) {
+                            checkLengths(p.getLengths());
+                        } else {
+                        	checkLengths(p.getLength());
+                        }
+                    }
+                } catch (InvalidItemStateException e) {
+                    LOG.debug("Property removed concurrently - ignoring", e);
+                }
+            }
+        } catch (InvalidItemStateException e) {
+            LOG.debug("Node removed concurrently - ignoring", e);
+        }
+        try {
+            for (NodeIterator it = n.getNodes(); it.hasNext();) {
+                recurse(it.nextNode(), sleep);
+            }
+        } catch (InvalidItemStateException e) {
+            LOG.debug("Node removed concurrently - ignoring", e);
+        }
+        checkObservationException();
+    }
+
+    private void rememberNode(String path) {
+        // Do nothing at the moment
+        // TODO It may be possible to delete some items early
+        /*
+         * To delete files early in the garbage collection scan, we could do
+         * this:
+         *
+         * A) If garbage collection was run before, see if there a file with the
+         * list of UUIDs ('uuids.txt').
+         *
+         * B) If yes, and if the checksum is ok, read all those nodes first (if
+         * not so many). This updates the modified date of all old files that
+         * are still in use. Afterwards, delete all files with an older modified
+         * date than the last scan! Newer files, and files that are read have a
+         * newer modification date.
+         *
+         * C) Delete the 'uuids.txt' file (in any case).
+         *
+         * D) Iterate (recurse) through all nodes and properties like now. If a
+         * node has a binary property, store the UUID of the node in the file
+         * ('uuids.txt'). Also store the time when the scan started.
+         *
+         * E) Checksum and close the file.
+         *
+         * F) Like now, delete files with an older modification date than this
+         * scan.
+         *
+         * We can't use node path for this, UUIDs are required as nodes could be
+         * moved around.
+         *
+         * This mechanism requires that all data stores update the last modified
+         * date when calling addRecord and that record already exists.
+         *
+         */
+    }
+
+    private static void checkLengths(long... lengths) throws RepositoryException {
+        for (long length : lengths) {
+            if (length == -1) {
+                throw new RepositoryException("mark failed to access a property");
+            }
+        }
+    }
+
+    public void close() {
+        if (!closed.getAndSet(true)) {
+            try {
+                stopScan();
+            } catch (RepositoryException e) {
+                LOG.warn("An error occured when stopping the event listener", e);
+            }
+            for (Session s : sessionList) {
+                s.logout();
+            }
+        }
+    }
+
+    private void checkObservationException() throws RepositoryException {
+        RepositoryException e = observationException;
+        if (e != null) {
+            observationException = null;
+            String message = "Exception while processing concurrent events";
+            LOG.warn(message, e);
+            e = new RepositoryException(message, e);
+        }
+    }
+
+    void onObservationException(Exception e) {
+        if (e instanceof RepositoryException) {
+            observationException = (RepositoryException) e;
+        } else {
+            observationException = new RepositoryException(e);
+        }
+    }
+
+    /**
+     * Auto-close in case the application didn't call it explicitly.
+     */
+    protected void finalize() throws Throwable {
+        close();
+        super.finalize();
+    }
+
+    /**
+     * Event listener to detect moved nodes.
+     * A SynchronousEventListener is used to make sure this method is called before the main iteration ends.
+     */
+    class Listener implements SynchronousEventListener {
+
+        private final GarbageCollector gc;
+        private final Session session;
+        private final ObservationManager manager;
+
+        Listener(GarbageCollector gc, Session session)
+                throws UnsupportedRepositoryOperationException,
+                RepositoryException {
+            this.gc = gc;
+            this.session = session;
+            Workspace ws = session.getWorkspace();
+            manager = ws.getObservationManager();
+            manager.addEventListener(this, Event.NODE_MOVED, "/", true, null,
+                    null, false);
+        }
+
+        void stop() throws RepositoryException {
+            manager.removeEventListener(this);
+            session.logout();
+        }
+
+        public void onEvent(EventIterator events) {
+            if (testDelay > 0) {
+                try {
+                    Thread.sleep(testDelay);
+                } catch (InterruptedException e) {
+                    // ignore
+                }
+            }
+            while (events.hasNext()) {
+                Event event = events.nextEvent();
+                try {
+                    String path = event.getPath();
+                    try {
+                        Item item = session.getItem(path);
+                        if (item.isNode()) {
+                            Node n = (Node) item;
+                            recurse(n, testDelay);
+                        }
+                    } catch (PathNotFoundException e) {
+                        // ignore
+                    }
+                } catch (Exception e) {
+                    gc.onObservationException(e);
+                    try {
+                        stop();
+                    } catch (RepositoryException e2) {
+                        LOG.warn("Exception removing the observation listener - ignored", e2);
+                    }
+                }
+            }
+        }
+    }
+
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/id/NodeId.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/id/NodeId.java
index bd6ccd3..8cbc63e 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/id/NodeId.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/id/NodeId.java
@@ -119,7 +119,7 @@ public class NodeId implements ItemId, Comparable<NodeId> {
      * Creates a node identifier from the given UUID string.
      *
      * @see #fromString(String)
-     * @param uuid UUID string
+     * @param uuidString UUID string
      * @throws IllegalArgumentException if the UUID string is invalid
      */
     public NodeId(String uuidString) throws IllegalArgumentException {
@@ -250,7 +250,31 @@ public class NodeId implements ItemId, Comparable<NodeId> {
      * @return UUID string
      */
     public String toString() {
-        return new UUID(msb, lsb).toString();
+        char[] retval = new char[36];
+        hex4(retval, 0, msb >>> 48);
+        hex4(retval, 4, msb >>> 32);
+        retval[8]  = '-';
+        hex4(retval, 9, msb >>> 16);
+        retval[13] = '-';
+        hex4(retval, 14, msb);
+        retval[18] = '-';
+        hex4(retval, 19, lsb >>> 48);
+        retval[23] = '-';
+        hex4(retval, 24, lsb >>> 32);
+        hex4(retval, 28, lsb >>> 16);
+        hex4(retval, 32, lsb);
+        return new String(retval);
+    }
+
+    private static final void hex4(char[] c, int index, long value) {
+        for (int i = 0; i < 4; i++) {
+            long v = (value >>> (12 - i * 4)) & 0xf;
+            if (v < 10) {
+                c[index + i] = (char) (v + '0');
+            } else {
+                c[index + i] = (char) (v - 10 + 'a');
+            }
+        }
     }
 
     /**
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/id/SeededSecureRandom.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/id/SeededSecureRandom.java
index fd30653..38fd2c9 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/id/SeededSecureRandom.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/id/SeededSecureRandom.java
@@ -1,110 +1,111 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.id;
-
-import java.security.SecureRandom;
-import java.util.Random;
-
-/**
- * Automatically seeded singleton secure random number generator.
- *
- * @see <a href="https://issues.apache.org/jira/browse/JCR-1206">JCR-1206</a>:
- *      UUID generation: SecureRandom should be used by default
- */
-class SeededSecureRandom extends SecureRandom implements Runnable {
-
-    /**
-     * Maximum number of milliseconds to wait for the seeding.
-     */
-    private static final int MAX_SEED_TIME = 1000;
-
-    /**
-     * Singleton instance of this class. Initialized when first accessed.
-     */
-    private static volatile Random instance = null;
-
-    /**
-     * Returns the singleton instance of this class. The instance is
-     * created and seeded when this method is first called.
-     *
-     * @return seeded secure random number generator
-     */
-    public static Random getInstance() {
-        if (instance == null) {
-            synchronized (SeededSecureRandom.class) {
-                if (instance == null) {
-                    instance = new SeededSecureRandom();
-                }
-            }
-        }
-        return instance;
-    }
-
-    /**
-     * Flag to indicate whether seeding is complete.
-     */
-    private volatile boolean seeded = false;
-
-    /**
-     * Creates and seeds a secure random number generator.
-     */
-    private SeededSecureRandom() {
-        // Can not do that in a static initializer block, because
-        // threads are not started after the initializer block exits
-        Thread thread = new Thread(this, "SeededSecureRandom");
-        thread.start();
-        try {
-            thread.join(MAX_SEED_TIME);
-        } catch (InterruptedException e) {
-            // ignore
-        }
-
-        if (!seeded) {
-            // Alternative seed algorithm if the default is very slow
-            setSeed(System.currentTimeMillis());
-            setSeed(System.nanoTime());
-            setSeed(new Object().hashCode());
-            Runtime runtime = Runtime.getRuntime();
-            setSeed(runtime.freeMemory());
-            setSeed(runtime.maxMemory());
-            setSeed(runtime.totalMemory());
-            setSeed(System.getProperties().toString().hashCode());
-
-            // Thread timing (a second thread is already running)
-            for (int j = 0; j < 16; j++) {
-                int i = 0;
-                long start = System.currentTimeMillis();
-                while (start == System.currentTimeMillis()) {
-                    i++;
-                }
-                // Supplement the existing seed
-                setSeed(i);
-            }
-        }
-    }
-
-    /**
-     * Seeds this random number generator with 32 bytes of random data.
-     * Run in an initializer thread as this may be slow on some systems, see
-     * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6202721.
-     */
-    public void run() {
-        setSeed(generateSeed(32));
-        seeded = true;
-    }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.id;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+/**
+ * Automatically seeded singleton secure random number generator.
+ *
+ * @see <a href="https://issues.apache.org/jira/browse/JCR-1206">JCR-1206</a>:
+ *      UUID generation: SecureRandom should be used by default
+ */
+class SeededSecureRandom extends SecureRandom implements Runnable {
+
+    /**
+     * Maximum number of milliseconds to wait for the seeding.
+     */
+    private static final int MAX_SEED_TIME = 1000;
+
+    /**
+     * Singleton instance of this class. Initialized when first accessed.
+     */
+    private static volatile Random instance = null;
+
+    /**
+     * Returns the singleton instance of this class. The instance is
+     * created and seeded when this method is first called.
+     *
+     * @return seeded secure random number generator
+     */
+    public static Random getInstance() {
+        if (instance == null) {
+            synchronized (SeededSecureRandom.class) {
+                if (instance == null) {
+                    instance = new SeededSecureRandom();
+                }
+            }
+        }
+        return instance;
+    }
+
+    /**
+     * Flag to indicate whether seeding is complete.
+     */
+    private volatile boolean seeded = false;
+
+    /**
+     * Creates and seeds a secure random number generator.
+     */
+    private SeededSecureRandom() {
+        // Can not do that in a static initializer block, because
+        // threads are not started after the initializer block exits
+        Thread thread = new Thread(this, "SeededSecureRandom");
+        thread.setDaemon(true);
+        thread.start();
+        try {
+            thread.join(MAX_SEED_TIME);
+        } catch (InterruptedException e) {
+            // ignore
+        }
+
+        if (!seeded) {
+            // Alternative seed algorithm if the default is very slow
+            setSeed(System.currentTimeMillis());
+            setSeed(System.nanoTime());
+            setSeed(new Object().hashCode());
+            Runtime runtime = Runtime.getRuntime();
+            setSeed(runtime.freeMemory());
+            setSeed(runtime.maxMemory());
+            setSeed(runtime.totalMemory());
+            setSeed(System.getProperties().toString().hashCode());
+
+            // Thread timing (a second thread is already running)
+            for (int j = 0; j < 16; j++) {
+                int i = 0;
+                long start = System.currentTimeMillis();
+                while (start == System.currentTimeMillis()) {
+                    i++;
+                }
+                // Supplement the existing seed
+                setSeed(i);
+            }
+        }
+    }
+
+    /**
+     * Seeds this random number generator with 32 bytes of random data.
+     * Run in an initializer thread as this may be slow on some systems, see
+     * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6202721.
+     */
+    public void run() {
+        setSeed(generateSeed(32));
+        seeded = true;
+    }
+
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/jmx/QueryStatManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/jmx/QueryStatManager.java
deleted file mode 100644
index 24abd2f..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/jmx/QueryStatManager.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.jmx;
-
-import javax.management.openmbean.CompositeDataSupport;
-import javax.management.openmbean.CompositeType;
-import javax.management.openmbean.OpenDataException;
-import javax.management.openmbean.OpenType;
-import javax.management.openmbean.SimpleType;
-import javax.management.openmbean.TabularData;
-import javax.management.openmbean.TabularDataSupport;
-import javax.management.openmbean.TabularType;
-
-import org.apache.jackrabbit.api.jmx.QueryStatManagerMBean;
-import org.apache.jackrabbit.api.stats.QueryStat;
-import org.apache.jackrabbit.api.stats.QueryStatDto;
-
-/**
- * The QueryStatManagerMBean default implementation
- * 
- */
-public class QueryStatManager implements QueryStatManagerMBean {
-
-    private final QueryStat queryStat;
-
-    public QueryStatManager(final QueryStat queryStat) {
-        this.queryStat = queryStat;
-    }
-
-    public boolean isEnabled() {
-        return this.queryStat.isEnabled();
-    }
-
-    public void enable() {
-        this.queryStat.setEnabled(true);
-    }
-
-    public void disable() {
-        this.queryStat.setEnabled(false);
-    }
-
-    public void reset() {
-        this.queryStat.reset();
-    }
-
-    public int getSlowQueriesQueueSize() {
-        return queryStat.getSlowQueriesQueueSize();
-    }
-
-    public void setSlowQueriesQueueSize(int size) {
-        this.queryStat.setSlowQueriesQueueSize(size);
-    }
-
-    public void clearSlowQueriesQueue() {
-        this.queryStat.clearSlowQueriesQueue();
-    }
-
-    public int getPopularQueriesQueueSize() {
-        return queryStat.getPopularQueriesQueueSize();
-    }
-
-    public void setPopularQueriesQueueSize(int size) {
-        queryStat.setPopularQueriesQueueSize(size);
-    }
-
-    public void clearPopularQueriesQueue() {
-        queryStat.clearPopularQueriesQueue();
-    }
-
-    public TabularData getSlowQueries() {
-        return asTabularData(queryStat.getSlowQueries());
-    }
-
-    public TabularData getPopularQueries() {
-        return asTabularData(queryStat.getPopularQueries());
-    }
-
-    private TabularData asTabularData(QueryStatDto[] data) {
-        TabularDataSupport tds = null;
-        try {
-            CompositeType ct = QueryStatCompositeTypeFactory.getCompositeType();
-
-            TabularType tt = new TabularType(QueryStatDto.class.getName(),
-                    "Query History", ct, QueryStatCompositeTypeFactory.index);
-            tds = new TabularDataSupport(tt);
-
-            for (QueryStatDto q : data) {
-                tds.put(new CompositeDataSupport(ct,
-                        QueryStatCompositeTypeFactory.names,
-                        QueryStatCompositeTypeFactory.getValues(q)));
-            }
-            return tds;
-        } catch (Exception e) {
-            e.printStackTrace();
-            return null;
-        }
-    }
-
-    private static class QueryStatCompositeTypeFactory {
-
-        private final static String[] index = { "position" };
-
-        private final static String[] names = { "position", "duration",
-                "occurrenceCount", "language", "statement", "creationTime" };
-
-        private final static String[] descriptions = { "position", "duration",
-                "occurrenceCount", "language", "statement", "creationTime" };
-
-        private final static OpenType[] types = { SimpleType.LONG,
-                SimpleType.LONG, SimpleType.INTEGER, SimpleType.STRING,
-                SimpleType.STRING, SimpleType.STRING };
-
-        public static CompositeType getCompositeType() throws OpenDataException {
-            return new CompositeType(QueryStat.class.getName(),
-                    QueryStat.class.getName(), names, descriptions, types);
-        }
-
-        public static Object[] getValues(QueryStatDto q) {
-            return new Object[] { q.getPosition(), q.getDuration(),
-                    q.getOccurrenceCount(), q.getLanguage(), q.getStatement(),
-                    q.getCreationTime() };
-        }
-    }
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/jndi/BindableRepository.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/jndi/BindableRepository.java
index ecc8e51..8108b2e 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/jndi/BindableRepository.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/jndi/BindableRepository.java
@@ -41,17 +41,17 @@ import javax.naming.Referenceable;
  * delays the instantiation of the actual Repository instance and
  * implements serialization and JNDI referenceability by keeping
  * track of the repository configuration parameters.
- * <p/>
+ * <p>
  * A BindableRepository instance contains the configuration file
  * and home directory paths of a Jackrabbit repository. The separate
  * {@link #init() init()} method is used to create a transient
  * {@link RepositoryImpl RepositoryImpl} instance to which all the
  * JCR API calls are delegated.
- * <p/>
+ * <p>
  * An instance of this class is normally always also initialized.
  * The uninitialized state is only used briefly during the static
  * construction, deserialization, and JNDI "referenciation".
- * <p/>
+ * <p>
  * A JVM shutdown hook is used to make sure that the initialized
  * repository is properly closed when the JVM shuts down. The
  * {@link RegistryHelper#unregisterRepository(javax.naming.Context, String)}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/AbstractJournal.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/AbstractJournal.java
index 72d193c..4829983 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/AbstractJournal.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/AbstractJournal.java
@@ -21,6 +21,7 @@ import java.io.InputStream;
 import java.util.HashMap;
 import java.util.Map;
 
+import org.apache.jackrabbit.core.util.XAReentrantWriterPreferenceReadWriteLock;
 import org.apache.jackrabbit.core.version.InternalVersionManagerImpl;
 import org.apache.jackrabbit.core.version.VersioningLock;
 import org.apache.jackrabbit.spi.commons.conversion.DefaultNamePathResolver;
@@ -29,9 +30,6 @@ import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import EDU.oswego.cs.dl.util.concurrent.ReadWriteLock;
-import EDU.oswego.cs.dl.util.concurrent.ReentrantWriterPreferenceReadWriteLock;
-
 /**
  * Base journal implementation.
  */
@@ -71,7 +69,7 @@ public abstract class AbstractJournal implements Journal {
      * Journal lock, allowing multiple readers (synchronizing their contents)
      * but only one writer (appending a new entry).
      */
-    private final ReadWriteLock rwLock = new ReentrantWriterPreferenceReadWriteLock();
+    private final XAReentrantWriterPreferenceReadWriteLock rwLock = new XAReentrantWriterPreferenceReadWriteLock();
 
     /**
      * The path of the local revision file on disk. Configurable through the repository.xml.
@@ -179,24 +177,41 @@ public abstract class AbstractJournal implements Journal {
         return minimalRevision;
     }
 
+    
     /**
      * {@inheritDoc}
      */
-    public void sync() throws JournalException {
-        if (internalVersionManager != null) {
-            VersioningLock.ReadLock lock =
-                internalVersionManager.acquireReadLock();
-            try {
-                internalSync();
-            } finally {
-                lock.release();
+    public void sync(boolean startup) throws JournalException {
+        for (;;) {
+            if (internalVersionManager != null) {
+                VersioningLock.ReadLock lock =
+                        internalVersionManager.acquireReadLock();
+                try {
+                    internalSync(startup);
+                } finally {
+                    lock.release();
+                }
+            } else {
+                internalSync(startup);
             }
-        } else {
-            internalSync();
+            // startup sync already done, don't do it again
+            startup = false;
+            if (syncAgainOnNewRecords()) {
+                // sync again if there are more records available
+                RecordIterator it = getRecords(getMinimalRevision());
+                try {
+                    if (it.hasNext()) {
+                        continue;
+                    }
+                } finally {
+                    it.close();
+                }
+            }
+            break;
         }
     }
 
-    private void internalSync() throws JournalException {
+    private void internalSync(boolean startup) throws JournalException {
         try {
             rwLock.readLock().acquire();
         } catch (InterruptedException e) {
@@ -204,59 +219,55 @@ public abstract class AbstractJournal implements Journal {
             throw new JournalException(msg, e);
         }
         try {
-            doSync(getMinimalRevision());
+            doSync(getMinimalRevision(), startup);
         } finally {
             rwLock.readLock().release();
         }
     }
 
+
+    protected void doSync(long startRevision, boolean startup) throws JournalException {
+        // by default ignore startup parameter for backwards compatibility
+        // only needed for persistence backend that need special treatment on startup.
+        doSync(startRevision);
+    }
+    
     /**
+     * 
      * Synchronize contents from journal. May be overridden by subclasses.
      *
      * @param startRevision start point (exclusive)
      * @throws JournalException if an error occurs
      */
     protected void doSync(long startRevision) throws JournalException {
-        for (;;) {
-            RecordIterator iterator = getRecords(startRevision);
-            long stopRevision = Long.MIN_VALUE;
-    
-            try {
-                while (iterator.hasNext()) {
-                    Record record = iterator.nextRecord();
-                    if (record.getJournalId().equals(id)) {
-                        log.info("Record with revision '" + record.getRevision()
-                                + "' created by this journal, skipped.");
-                    } else {
-                        RecordConsumer consumer = getConsumer(record.getProducerId());
-                        if (consumer != null) {
-                            try {
-                                consumer.consume(record);
-                            } catch (IllegalStateException e) {
-                                log.error("Could not synchronize to revision: " + record.getRevision() + " due illegal state of RecordConsumer.");
-                                return;
-                            }
-                        }
+        RecordIterator iterator = getRecords(startRevision);
+        long stopRevision = Long.MIN_VALUE;
+
+        try {
+            while (iterator.hasNext()) {
+                Record record = iterator.nextRecord();
+                if (record.getJournalId().equals(id)) {
+                    log.debug("Record with revision '" + record.getRevision()
+                            + "' created by this journal, skipped.");
+                } else {
+                    RecordConsumer consumer = getConsumer(record.getProducerId());
+                    if (consumer != null) {
+                        consumer.consume(record);
                     }
-                    stopRevision = record.getRevision();
                 }
-            } finally {
-                iterator.close();
+                stopRevision = record.getRevision();
             }
-    
-            if (stopRevision > 0) {
-                for (RecordConsumer consumer : consumers.values()) {
-                    consumer.setRevision(stopRevision);
-                }
-                log.info("Synchronized to revision: " + stopRevision);
+        } catch (IllegalStateException e) {
+            log.error("Could not synchronize to revision: " + (stopRevision + 1) + " due illegal state of RecordConsumer.");
+        } finally {
+            iterator.close();
+        }
 
-                if (syncAgainOnNewRecords()) {
-                    // changes detected, sync again
-                    startRevision = stopRevision;
-                    continue;
-                }
+        if (stopRevision > 0) {
+            for (RecordConsumer consumer : consumers.values()) {
+                consumer.setRevision(stopRevision);
             }
-            break;
+            log.debug("Synchronized from revision " + startRevision + " to revision: " + stopRevision);
         }
     }
     
@@ -273,7 +284,7 @@ public abstract class AbstractJournal implements Journal {
 
     /**
      * Lock the journal revision, disallowing changes from other sources until
-     * {@link #unlock has been called, and synchronizes to the latest change.
+     * {@link #unlock} has been called, and synchronizes to the latest change.
      *
      * @throws JournalException if an error occurs
      */
@@ -327,9 +338,13 @@ public abstract class AbstractJournal implements Journal {
      *                   successful
      */
     public void unlock(boolean successful) {
-        doUnlock(successful);
-
-        rwLock.writeLock().release();
+    	try {
+    		doUnlock(successful);
+    	} finally {
+    		//Should not happen that a RuntimeException will be thrown in subCode, but it's safer
+    		//to release the rwLock in finally block.
+            rwLock.writeLock().release();
+    	}
     }
 
     /**
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/AppendRecord.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/AppendRecord.java
index ab49550..1231ffb 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/AppendRecord.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/AppendRecord.java
@@ -16,19 +16,19 @@
  */
 package org.apache.jackrabbit.core.journal;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
+import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.DataOutputStream;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.BufferedOutputStream;
+
+import org.apache.jackrabbit.core.data.db.ResettableTempFileInputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Default temporary record used for appending to some journal.
@@ -253,7 +253,7 @@ public class AppendRecord extends AbstractRecord {
     /**
      * {@inheritDoc}
      */
-    public void update() throws JournalException {
+    public long update() throws JournalException {
         boolean succeeded = false;
 
         try {
@@ -265,6 +265,7 @@ public class AppendRecord extends AbstractRecord {
             try {
                 journal.append(this, in, length);
                 succeeded = true;
+                return length;
             } finally {
                 try {
                     in.close();
@@ -297,7 +298,7 @@ public class AppendRecord extends AbstractRecord {
     private InputStream openInput() throws JournalException {
         if (file != null) {
             try {
-                return new FileInputStream(file);
+                return new ResettableTempFileInputStream(file);
             } catch (IOException e) {
                 String msg = "Unable to open file input on: " + file.getPath();
                 throw new JournalException(msg, e);
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/DatabaseJournal.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/DatabaseJournal.java
index 7ba58ab..52224f6 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/DatabaseJournal.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/DatabaseJournal.java
@@ -44,7 +44,7 @@ import javax.sql.DataSource;
  * <code>JOURNAL</code>, whereas the table <code>GLOBAL_REVISION</code> contains the
  * highest available revision number. These tables are located inside the schema specified
  * in <code>schemaObjectPrefix</code>.
- * <p/>
+ * <p>
  * It is configured through the following properties:
  * <ul>
  * <li><code>driver</code>: the JDBC driver class name to use; this is a required
@@ -364,7 +364,7 @@ public class DatabaseJournal extends AbstractJournal implements DatabaseAware {
         // Get the local file revision from disk (upgrade; see JCR-1087)
         long localFileRevision = 0L;
         if (getRevision() != null) {
-            InstanceRevision currentFileRevision = new FileRevision(new File(getRevision()));
+            InstanceRevision currentFileRevision = new FileRevision(new File(getRevision()), true);
             localFileRevision = currentFileRevision.get();
             currentFileRevision.close();
         }
@@ -437,30 +437,37 @@ public class DatabaseJournal extends AbstractJournal implements DatabaseAware {
 
     /**
      * Synchronize contents from journal. May be overridden by subclasses.
-     * Override to do it in batchMode, since some databases (PSQL) when
+     * Do the initial sync in batchMode, since some databases (PSQL) when
      * not in transactional mode, load all results in memory which causes
-     * out of memory.
+     * out of memory. See JCR-2832
      *
      * @param startRevision start point (exclusive)
+     * @param startup indicates if the cluster node is syncing on startup 
+     *        or does a normal sync.
      * @throws JournalException if an error occurs
      */
     @Override
-    protected void doSync(long startRevision) throws JournalException {
-        try {
-            startBatch();
+    protected void doSync(long startRevision, boolean startup) throws JournalException {
+        if (!startup) {
+            // if the cluster node is not starting do a normal sync
+            doSync(startRevision);
+        } else {
             try {
-                super.doSync(startRevision);
-            } finally {
-                endBatch(true);
+                startBatch();
+                try {
+                    doSync(startRevision);
+                } finally {
+                    endBatch(true);
+                }
+            } catch (SQLException e) {
+                throw new JournalException("Couldn't sync the cluster node", e);
             }
-        } catch (SQLException e) {
-            throw new JournalException("Couldn't sync the cluster node", e);
         }
     }
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * This journal is locked by incrementing the current value in the table
      * named <code>GLOBAL_REVISION</code>, which effectively write-locks this
      * table. The updated value is then saved away and remembered in the
@@ -519,7 +526,7 @@ public class DatabaseJournal extends AbstractJournal implements DatabaseAware {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Save away the locked revision inside the newly appended record.
      */
     protected void appending(AppendRecord record) {
@@ -528,7 +535,7 @@ public class DatabaseJournal extends AbstractJournal implements DatabaseAware {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * We have already saved away the revision for this record.
      */
     protected void append(AppendRecord record, InputStream in, int length)
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/FileJournal.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/FileJournal.java
index 8843a20..af0fe02 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/FileJournal.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/FileJournal.java
@@ -247,7 +247,7 @@ public class FileJournal extends AbstractJournal {
      * {@inheritDoc}
      */
     public InstanceRevision getInstanceRevision() throws JournalException {
-        return new FileRevision(new File(getRevision()));
+        return new FileRevision(new File(getRevision()), true);
     }
 
     /**
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/FileRevision.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/FileRevision.java
index 13c700f..ce873b1 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/FileRevision.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/FileRevision.java
@@ -36,25 +36,34 @@ public class FileRevision implements InstanceRevision {
     /**
      * Underlying random access file.
      */
-    private final RandomAccessFile raf;
+    protected final RandomAccessFile raf;
+
+    /**
+     * Flag indicating whether to sync the file on every write.
+     */
+    protected final boolean sync;
 
     /**
      * Cached value.
      */
-    private long value;
+    protected long value;
     
     /**
      * Flag indicating whether this revision file is closed.
      */
-    private boolean closed;
+    protected boolean closed;
 
     /**
      * Creates a new file based revision counter.
      *
      * @param file holding global counter
+     * @param sync whether to sync the file on every write
+     * 
      * @throws JournalException if some error occurs
      */
-    public FileRevision(File file) throws JournalException {
+    public FileRevision(File file, boolean sync) throws JournalException {
+        this.sync = sync;
+
         try {
             if (!file.exists()) {
                 file.createNewFile();
@@ -101,7 +110,9 @@ public class FileRevision implements InstanceRevision {
             }
             raf.seek(0L);
             raf.writeLong(value);
-            raf.getFD().sync();
+            if (sync) {
+                raf.getFD().sync();
+            }
             this.value = value;
         } catch (IOException e) {
             throw new JournalException("I/O error occurred.", e);
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/Journal.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/Journal.java
index 057888c..3a2d759 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/Journal.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/Journal.java
@@ -54,10 +54,15 @@ public interface Journal {
      * revision with the revisions of all registered consumers and invoke
      * their {@link RecordConsumer#consume} method when their identifier
      * matches the one found in the records.
+     * The startup flag allow for a separate treatment of the initial sync
+     * when the cluster nodes starts up. This might be needed for example
+     * when there are a lot of old revisions in a database.
      *
+     * @param startup indicates if the cluster node is syncing on startup 
+     *        or does a normal sync.
      * @throws JournalException if an error occurs
      */
-    void sync() throws JournalException;
+    void sync(boolean startup) throws JournalException;
 
     /**
      * Return the record producer for a given identifier.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/OracleDatabaseJournal.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/OracleDatabaseJournal.java
index 8919b1f..43a4f00 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/OracleDatabaseJournal.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/OracleDatabaseJournal.java
@@ -86,7 +86,7 @@ public class OracleDatabaseJournal extends DatabaseJournal {
     
     /**
      * Sets the Oracle tablespace for indexes.
-     * @param tablespace the Oracle tablespace for indexes.
+     * @param tablespaceName the Oracle tablespace for indexes.
      */
     public void setIndexTablespace(String tablespaceName) {
         this.indexTablespace = this.buildTablespaceClause(tablespaceName);
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/ReadRecord.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/ReadRecord.java
index aa5560e..84a55e4 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/ReadRecord.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/ReadRecord.java
@@ -271,7 +271,7 @@ public class ReadRecord extends AbstractRecord {
         throw unsupported();
     }
 
-    public void update() throws JournalException {
+    public long update() throws JournalException {
         throw unsupported();
     }
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/Record.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/Record.java
index 68e7fe3..a740232 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/Record.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/journal/Record.java
@@ -277,10 +277,11 @@ public interface Record {
      * Update the changes made to an appended record. This will also update
      * this record's revision.
      *
+     * @returns The update size in bytes.
      * @throws JournalException if this record has not been appended,
      *                          or if another error occurs
      */
-    void update() throws JournalException;
+    long update() throws JournalException;
 
     /**
      * Cancel the changes made to an appended record.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockImpl.java
index bda39d1..7fe701e 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockImpl.java
@@ -22,6 +22,7 @@ import org.apache.jackrabbit.core.security.authorization.Permission;
 
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
+import javax.jcr.Session;
 import javax.jcr.lock.LockException;
 
 /**
@@ -78,7 +79,7 @@ class LockImpl implements javax.jcr.lock.Lock {
      * {@inheritDoc}
      */
     public String getLockToken() {
-        if (!info.isSessionScoped() && info.isLockHolder(node.getSession())) {
+        if (!info.isSessionScoped() && (info.isLockHolder(node.getSession()) || isAdminUser(node.getSession()))) {
             return info.getLockToken();
         } else {
             return null;
@@ -151,4 +152,15 @@ class LockImpl implements javax.jcr.lock.Lock {
         return info.isLockHolder(node.getSession());
     }
 
+    /**
+     * Check whether a session belongs to an administrative user.
+     */
+    private boolean isAdminUser(Session session) {
+        if (session instanceof SessionImpl) {
+            return ((SessionImpl) session).isAdmin();
+        } else {
+            // fallback. use hardcoded default admin ID
+            return "admin".equals(session.getUserID());
+        }
+    }
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java
index 2eed9b7..e4e1471 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/LockManagerImpl.java
@@ -16,16 +16,32 @@
  */
 package org.apache.jackrabbit.core.lock;
 
-import EDU.oswego.cs.dl.util.concurrent.ReentrantLock;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Workspace;
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+
 import org.apache.commons.collections.map.LinkedMap;
 import org.apache.commons.io.IOUtils;
-import org.apache.jackrabbit.core.id.ItemId;
-import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.NodeImpl;
-import org.apache.jackrabbit.core.id.PropertyId;
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.SessionListener;
-import org.apache.jackrabbit.core.TransactionContext;
 import org.apache.jackrabbit.core.WorkspaceImpl;
 import org.apache.jackrabbit.core.cluster.ClusterOperation;
 import org.apache.jackrabbit.core.cluster.LockEventChannel;
@@ -33,43 +49,24 @@ import org.apache.jackrabbit.core.cluster.LockEventListener;
 import org.apache.jackrabbit.core.fs.FileSystem;
 import org.apache.jackrabbit.core.fs.FileSystemException;
 import org.apache.jackrabbit.core.fs.FileSystemResource;
+import org.apache.jackrabbit.core.id.ItemId;
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.id.PropertyId;
 import org.apache.jackrabbit.core.observation.EventImpl;
 import org.apache.jackrabbit.core.observation.SynchronousEventListener;
 import org.apache.jackrabbit.core.state.ItemStateException;
 import org.apache.jackrabbit.core.state.NodeState;
 import org.apache.jackrabbit.core.state.PropertyState;
 import org.apache.jackrabbit.core.state.UpdatableItemStateManager;
+import org.apache.jackrabbit.core.util.XAReentrantLock;
 import org.apache.jackrabbit.core.value.InternalValue;
 import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
 import org.apache.jackrabbit.spi.commons.name.PathMap;
-import javax.jcr.Workspace;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.lock.Lock;
-import javax.jcr.lock.LockException;
-import javax.jcr.observation.Event;
-import javax.jcr.observation.EventIterator;
-import javax.transaction.xa.Xid;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-
 /**
  * Provides the functionality needed for locking and unlocking nodes.
  */
@@ -92,72 +89,14 @@ public class LockManagerImpl
     private final PathMap<LockInfo> lockMap = new PathMap<LockInfo>();
 
     /**
-     * Thread aware lock to path map.
+     * XA/Thread aware lock to path map.
      */
-    private final ReentrantLock lockMapLock = new ReentrantLock();
+    private final XAReentrantLock lockMapLock = new XAReentrantLock();
     
     /**
-     * Xid aware lock to path map.
+     * XA/Thread aware lock for lock properties
      */
-    private final ReentrantLock xidlockMapLock = new ReentrantLock(){
-
-    	/**
-    	 * The active Xid of this {@link ReentrantLock}
-    	 */
-        private Xid activeXid;
-
-        /**
-         * Check if the given Xid comes from the same globalTX
-         * @param otherXid
-         * @return true if same globalTX otherwise false
-         */
-        boolean isSameGlobalTx(Xid otherXid) {
-    	    return (activeXid == otherXid) || Arrays.equals(activeXid.getGlobalTransactionId(), otherXid.getGlobalTransactionId());
-    	}
-        
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void acquire() throws InterruptedException {
-            if (Thread.interrupted()) {
-                throw new InterruptedException();
-            }
-            Xid currentXid = TransactionContext.getCurrentXid();
-            synchronized(this) {
-                if (currentXid == activeXid || (activeXid != null && isSameGlobalTx(currentXid))) { 
-                    ++holds_;
-                } else {
-                    try {  
-                        while (activeXid != null) {
-                            wait(); 
-                        }
-                        activeXid = currentXid;
-                        holds_ = 1;
-                    } catch (InterruptedException ex) {
-                        notify();
-                        throw ex;
-                    }
-                }
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public synchronized void release()  {
-            Xid currentXid = TransactionContext.getCurrentXid();
-            if (activeXid != null && !isSameGlobalTx(currentXid)) {
-                throw new Error("Illegal Lock usage"); 
-            }
-
-            if (--holds_ == 0) {
-                activeXid = null;
-                notify(); 
-            }
-        }
-    };
+    private XAReentrantLock lockPropertiesLock = new XAReentrantLock();
 
     /**
      * The periodically invoked lock timeout handler.
@@ -232,26 +171,32 @@ public class LockManagerImpl
      *      JSR 283: Locking
      */
     private class TimeoutHandler implements Runnable {
+        private final TimeoutHandlerVisitor visitor = new TimeoutHandlerVisitor();
+
         public void run() {
-            lockMap.traverse(new PathMap.ElementVisitor<LockInfo>() {
-                public void elementVisited(PathMap.Element<LockInfo> element) {
-                    LockInfo info = element.get();
-                    if (info != null && info.isLive() && info.isExpired()) {
-                        NodeId id = info.getId();
-                        SessionImpl holder = info.getLockHolder();
-                        if (holder == null) {
-                            info.setLockHolder(sysSession);
-                            holder = sysSession;
-                        }
-                        try {
-                            // FIXME: This session access is not thread-safe!
-                            unlock(holder.getNodeById(id));
-                        } catch (RepositoryException e) {
-                            log.warn("Unable to expire the lock " + id, e);
-                        }
-                    }
+            lockMap.traverse(visitor, false);
+        }
+    }
+
+    private class TimeoutHandlerVisitor implements
+            PathMap.ElementVisitor<LockInfo> {
+        public void elementVisited(PathMap.Element<LockInfo> element) {
+            LockInfo info = element.get();
+            if (info != null && info.isLive() && info.isExpired()) {
+                NodeId id = info.getId();
+                SessionImpl holder = info.getLockHolder();
+                if (holder == null) {
+                    info.setLockHolder(sysSession);
+                    holder = sysSession;
+                }
+                try {
+                    // FIXME: This session access is not thread-safe!
+                    log.debug("Try to unlock expired lock. NodeId {}", id);
+                    unlock(holder.getNodeById(id));
+                } catch (RepositoryException e) {
+                    log.warn("Unable to expire the lock. NodeId " + id, e);
                 }
-            }, false);
+            }
         }
     }
 
@@ -279,7 +224,7 @@ public class LockManagerImpl
     }
 
     /**
-     * Reapply a lock given a lock token that was read from the locks file
+     * Reaply a lock given a lock token that was read from the locks file
      *
      * @param lockTokenLine lock token to apply
      */
@@ -297,6 +242,8 @@ public class LockManagerImpl
         }
 
         try {
+        	acquire();
+        	
             NodeId id = LockInfo.parseLockToken(parts[0]);
             NodeImpl node = (NodeImpl) sysSession.getItemManager().getItem(id);
             Path path = getPath(sysSession, id);
@@ -311,6 +258,8 @@ public class LockManagerImpl
         } catch (RepositoryException e) {
             log.warn("Unable to recreate lock '" + token + "': " + e.getMessage());
             log.debug("Root cause: ", e);
+        } finally {
+        	release();
         }
     }
 
@@ -638,7 +587,7 @@ public class LockManagerImpl
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * In order to prevent deadlocks from within the synchronous dispatching of
      * events, content modifications should not be made from within code
      * sections that hold monitors. (see #JCR-194)
@@ -712,12 +661,17 @@ public class LockManagerImpl
     public void checkLock(Path path, Session session)
             throws LockException, RepositoryException {
 
-        PathMap.Element<LockInfo> element = lockMap.map(path, false);
-        LockInfo info = element.get();
-        if (info != null) {
-            if (element.hasPath(path) || info.isDeep()) {
-                checkLock(info, session);
+        acquire();
+        try {
+            PathMap.Element<LockInfo> element = lockMap.map(path, false);
+            LockInfo info = element.get();
+            if (info != null) {
+                if (element.hasPath(path) || info.isDeep()) {
+                    checkLock(info, session);
+                }
             }
+        } finally {
+            release();
         }
     }
 
@@ -725,7 +679,7 @@ public class LockManagerImpl
      * Check whether a lock info allows access to a session. May be overridden
      * by subclasses to allow access to nodes for sessions other than the
      * lock holder itself.
-     * <p/>
+     * <p>
      * Default implementation allows access to the lock holder only.
      *
      * @param info info to check
@@ -746,25 +700,30 @@ public class LockManagerImpl
      */
     public void checkUnlock(Session session, NodeImpl node)
             throws LockException, RepositoryException {
-
-        // check whether node is locked by this session
-        PathMap.Element<LockInfo> element =
-            lockMap.map(getPath((SessionImpl) session, node.getId()), true);
-        if (element == null) {
-            throw new LockException("Node not locked: " + node);
-        }
-        LockInfo info = element.get();
-        if (info == null) {
-            throw new LockException("Node not locked: " + node);
+        acquire();
+        
+        try {
+            // check whether node is locked by this session
+            PathMap.Element<LockInfo> element =
+                lockMap.map(getPath((SessionImpl) session, node.getId()), true);
+            if (element == null) {
+                throw new LockException("Node not locked: " + node);
+            }
+            LockInfo info = element.get();
+            if (info == null) {
+                throw new LockException("Node not locked: " + node);
+            }
+            checkUnlock(info, session);
+        } finally {
+            release();
         }
-        checkUnlock(info, session);
     }
 
     /**
      * Check whether a session is allowed to unlock a node. May be overridden
      * by subclasses to allow this to sessions other than the lock holder
      * itself.
-     * <p/>
+     * <p>
      * Default implementation allows unlocking to the lock holder only.
      *
      * @param info info to check
@@ -786,6 +745,8 @@ public class LockManagerImpl
      */
     public void addLockToken(SessionImpl session, String lt) throws LockException, RepositoryException {
         try {
+            acquire();
+            
             NodeId id = LockInfo.parseLockToken(lt);
 
             NodeImpl node = (NodeImpl) sysSession.getItemManager().getItem(id);
@@ -812,6 +773,8 @@ public class LockManagerImpl
             String msg = "Bad lock token: " + e.getMessage();
             log.warn(msg);
             throw new LockException(msg);
+        } finally {
+            release();
         }
     }
 
@@ -822,6 +785,8 @@ public class LockManagerImpl
             throws LockException, RepositoryException {
 
         try {
+            acquire();
+            
             NodeId id = LockInfo.parseLockToken(lt);
 
             NodeImpl node = (NodeImpl) sysSession.getItemManager().getItem(id);
@@ -845,6 +810,8 @@ public class LockManagerImpl
             String msg = "Bad lock token: " + e.getMessage();
             log.warn(msg);
             throw new LockException(msg);
+        } finally {
+            release();
         }
     }
 
@@ -862,11 +829,7 @@ public class LockManagerImpl
     private void acquire() {
         for (;;) {
             try {
-            	if (TransactionContext.getCurrentXid() == null) {
-            		lockMapLock.acquire();
-            	} else {
-            		xidlockMapLock.acquire();
-            	}
+           		lockMapLock.acquire();
                 break;
             } catch (InterruptedException e) {
                 // ignore
@@ -878,11 +841,28 @@ public class LockManagerImpl
      * Release lock on the lock map.
      */
     private void release() {
-    	if (TransactionContext.getCurrentXid() == null) {
-    		lockMapLock.release();
-    	} else {
-    		xidlockMapLock.release();
-    	}
+   		lockMapLock.release();
+    }
+
+    /**
+     * Acquire lock for modifying lock properties
+     */
+    private void acquireLockPropertiesLock() {
+        for (;;) {
+            try {
+                lockPropertiesLock.acquire();
+                break;
+            } catch (InterruptedException e) {
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * Release lock on the lockPropertiesLock.
+     */
+    private void releaseLockPropertiesLock() {
+        lockPropertiesLock.release();
     }
 
     /**
@@ -926,7 +906,9 @@ public class LockManagerImpl
         WorkspaceImpl wsp = (WorkspaceImpl) editingSession.getWorkspace();
         UpdatableItemStateManager stateMgr = wsp.getItemStateManager();
 
-        synchronized (stateMgr) {
+        try {
+            acquireLockPropertiesLock();
+
             if (stateMgr.inEditMode()) {
                 throw new RepositoryException("Unable to write lock properties.");
             }
@@ -975,6 +957,8 @@ public class LockManagerImpl
                     }
                 }
             }
+        } finally {
+            releaseLockPropertiesLock();
         }
     }
 
@@ -990,16 +974,17 @@ public class LockManagerImpl
         WorkspaceImpl wsp = (WorkspaceImpl) editingSession.getWorkspace();
         UpdatableItemStateManager stateMgr = wsp.getItemStateManager();
 
-        synchronized (stateMgr) {
+        try {
+            acquireLockPropertiesLock();
+
+            // add properties to content
+            if (stateMgr.inEditMode()) {
+                throw new RepositoryException("Unable to remove lock properties.");
+            }
+            stateMgr.edit();
             try {
-                // add properties to content
                 NodeId nodeId = node.getNodeId();
                 NodeState nodeState = (NodeState) stateMgr.getItemState(nodeId);
-
-                if (stateMgr.inEditMode()) {
-                    throw new RepositoryException("Unable to remove lock properties.");
-                }
-                stateMgr.edit();
                 if (nodeState.hasPropertyName(NameConstants.JCR_LOCKOWNER)) {
                     PropertyState propState = (PropertyState) stateMgr.getItemState(new PropertyId(nodeId, NameConstants.JCR_LOCKOWNER));
                     nodeState.removePropertyName(NameConstants.JCR_LOCKOWNER);
@@ -1024,6 +1009,8 @@ public class LockManagerImpl
                     stateMgr.cancel();
                 }
             }
+        } finally {
+            releaseLockPropertiesLock();
         }
     }
 
@@ -1131,7 +1118,8 @@ public class LockManagerImpl
      * add and remove operations on nodes with the same UUID into a move
      * operation.
      */
-    private Iterator<HierarchyEvent> consolidateEvents(EventIterator events) {
+    @SuppressWarnings("unchecked")
+	private Iterator<HierarchyEvent> consolidateEvents(EventIterator events) {
         LinkedMap eventMap = new LinkedMap();
 
         while (events.hasNext()) {
@@ -1183,6 +1171,8 @@ public class LockManagerImpl
         for (int i = 0; i < infos.size(); i++) {
             LockInfo info = infos.get(i);
             try {
+            	acquire();
+            	
                 NodeImpl node = (NodeImpl) sysSession.getItemManager().getItem(
                         info.getId());
                 lockMap.put(node.getPrimaryPath(), info);
@@ -1191,6 +1181,8 @@ public class LockManagerImpl
                 if (!info.isSessionScoped()) {
                     needsSave = true;
                 }
+            } finally {
+            	release();
             }
         }
 
@@ -1290,7 +1282,7 @@ public class LockManagerImpl
 
         /**
          * {@inheritDoc}
-         * <p/>
+         * <p>
          * When the owning session is logging out, we have to perform some
          * operations depending on the lock type.
          * (1) If the lock was session-scoped, we unlock the node.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/XAEnvironment.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/XAEnvironment.java
index f0cd3a9..9542408 100755
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/XAEnvironment.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/XAEnvironment.java
@@ -16,11 +16,11 @@
  */
 package org.apache.jackrabbit.core.lock;
 
-import org.apache.jackrabbit.core.TransactionException;
 import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.WorkspaceImpl;
 import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.data.core.TransactionException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -512,7 +512,7 @@ class XAEnvironment {
 
         /**
          * {@inheritDoc}
-         * <p/>
+         * <p>
          * As long as the XA environment is neither committed nor rolled back,
          * associated lock information is subject to change.
          */
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/XALockImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/XALockImpl.java
index 8550084..fd696ba 100755
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/XALockImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/XALockImpl.java
@@ -50,7 +50,7 @@ class XALockImpl extends LockImpl {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Refresh lock information if XA environment has changed.
      */
     @Override
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/XALockManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/XALockManager.java
index 23ce601..7a9589d 100755
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/XALockManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/lock/XALockManager.java
@@ -16,11 +16,11 @@
  */
 package org.apache.jackrabbit.core.lock;
 
-import org.apache.jackrabbit.core.InternalXAResource;
 import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.SessionImpl;
-import org.apache.jackrabbit.core.TransactionContext;
-import org.apache.jackrabbit.core.TransactionException;
+import org.apache.jackrabbit.data.core.InternalXAResource;
+import org.apache.jackrabbit.data.core.TransactionContext;
+import org.apache.jackrabbit.data.core.TransactionException;
 import org.apache.jackrabbit.spi.Path;
 
 import javax.jcr.RepositoryException;
@@ -265,7 +265,7 @@ public class XALockManager implements LockManager, InternalXAResource {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * This will finish the update and unlock the shared lock manager.
      */
     public void commit(TransactionContext tx) {
@@ -277,7 +277,7 @@ public class XALockManager implements LockManager, InternalXAResource {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * This will undo all updates and unlock the shared lock manager.
      */
     public void rollback(TransactionContext tx) {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/BitSetENTCacheImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/BitSetENTCacheImpl.java
index 15b1734..267da10 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/BitSetENTCacheImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/BitSetENTCacheImpl.java
@@ -109,7 +109,9 @@ public class BitSetENTCacheImpl implements EffectiveNodeTypeCache {
         if (contains(key)) {
             return key;
         }
-        Iterator<Key> iter = sortedKeys.iterator();
+        // clone TreeSet first to prevent ConcurrentModificationException
+        TreeSet<Key> keys = (TreeSet<Key>) sortedKeys.clone();
+        Iterator<Key> iter = keys.iterator();
         while (iter.hasNext()) {
             Key k = iter.next();
             if (key.contains(k)) {
@@ -408,8 +410,8 @@ public class BitSetENTCacheImpl implements EffectiveNodeTypeCache {
                         long h1 = w1 >>> 32;
                         long h2 = w2 >>> 32;
                         if (h1 == h2) {
-                            h1 = w1 & 0x0ffffL;
-                            h2 = w2 & 0x0ffffL;
+                            h1 = w1 & 0x0ffffffffL;
+                            h2 = w2 & 0x0ffffffffL;
                         }
                         return Long.signum(h2 - h1);
                     }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeType.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeType.java
index 3fdf931..4d30d76 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeType.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeType.java
@@ -43,7 +43,7 @@ import java.util.HashSet;
  * An <code>EffectiveNodeType</code> represents one or more
  * <code>NodeType</code>s as one 'effective' node type where inheritance
  * is resolved.
- * <p/>
+ * <p>
  * Instances of <code>EffectiveNodeType</code> are immutable.
  */
 public class EffectiveNodeType implements Cloneable {
@@ -81,7 +81,7 @@ public class EffectiveNodeType implements Cloneable {
 
     /**
      * Package private factory method.
-     * <p/>
+     * <p>
      * Creates an effective node type representation of a node type definition.
      * Note that the definitions of all referenced node types must be contained
      * in <code>ntdCache</code>.
@@ -585,7 +585,7 @@ public class EffectiveNodeType implements Cloneable {
     /**
      * Tests if the value constraints defined in the property definition
      * <code>pd</code> are satisfied by the the specified <code>values</code>.
-     * <p/>
+     * <p>
      * Note that the <i>protected</i> flag is not checked. Also note that no
      * type conversions are attempted if the type of the given values does not
      * match the required type as specified in the given definition.
@@ -971,7 +971,7 @@ public class EffectiveNodeType implements Cloneable {
     /**
      * Internal helper method which merges another <code>EffectiveNodeType</code>
      * instance with <i>this</i> instance.
-     * <p/>
+     * <p>
      * Warning: This instance might be in an inconsistent state if an exception
      * is thrown.
      *
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeTypeCache.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeTypeCache.java
index dea1220..e09ef41 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeTypeCache.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeTypeCache.java
@@ -109,7 +109,7 @@ public interface EffectiveNodeTypeCache extends Cloneable {
         /**
          * Creates a new key as a result of a subtract operation. i.e. removes all
          * node type names that from the other key.
-         * <p/>
+         * <p>
          * Please note that no exception is thrown if the other key has node type
          * names that are not contained in this key (i.e. {@link #contains(Key)}
          * returns <code>false</code>).
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeTypeCacheImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeTypeCacheImpl.java
index 9b1d197..1fcc6c7 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeTypeCacheImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/EffectiveNodeTypeCacheImpl.java
@@ -193,22 +193,22 @@ public class EffectiveNodeTypeCacheImpl implements EffectiveNodeTypeCache {
      * aggregate (e.g. an aggregation of multiple complex node types with deep
      * inheritance trees is more costly to build/validate than an aggregation
      * of two very simple node types with just one property definition each).
-     * <p/>
+     * <p>
      * A very simple (and not very accurate) approximation of the weight would
      * be the number of explicitly aggregated node types (ignoring inheritance
      * and complexity of each involved node type). A better approximation would
      * be the number of <b>all</b>, explicitly and implicitly (note that
      * inheritance is also an aggregation) aggregated node types.
-     * <p/>
+     * <p>
      * The more accurate the weight definition, the more efficient is the
      * the building of new aggregates.
-     * <p/>
+     * <p>
      * It is important to note that the weight is not part of the key value,
      * i.e. it is not considered by the <code>hashCode()</code> and
      * <code>equals(Object)</code> methods. It does however affect the order
      * of <code>WeightedKey</code> instances. See
      * <code>{@link #compareTo(Object)}</code> for more information.
-     * <p/>
+     * <p>
      * Let's assume we have an aggregation of node types named "b", "a" and "c".
      * Its key would be "[a, b, c]" and the weight 3 (using the simple
      * approximation).
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeDefinitionImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeDefinitionImpl.java
index 9a4bc46..817bcc8 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeDefinitionImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeDefinitionImpl.java
@@ -69,7 +69,7 @@ public class NodeTypeDefinitionImpl implements NodeTypeDefinition {
 
     /**
      * Returns the names of the supertypes actually declared in this node type.
-     * <p/>
+     * <p>
      * In implementations that support node type registration, if this
      * <code>NodeTypeDefinition</code> object is actually a newly-created empty
      * <code>NodeTypeTemplate</code>, then this method will return an array
@@ -97,11 +97,11 @@ public class NodeTypeDefinitionImpl implements NodeTypeDefinition {
     /**
      * Returns <code>true</code> if this is an abstract node type; returns
      * <code>false</code> otherwise.
-     * <p/>
+     * <p>
      * An abstract node type is one that cannot be assigned as the primary or
      * mixin type of a node but can be used in the definitions of other node
      * types as a superclass.
-     * <p/>
+     * <p>
      * In implementations that support node type registration, if this
      * <code>NodeTypeDefinition</code> object is actually a newly-created empty
      * <code>NodeTypeTemplate</code>, then this method will return
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeImpl.java
index a773f97..567069d 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeImpl.java
@@ -60,7 +60,7 @@ public class NodeTypeImpl extends AbstractNodeType implements NodeType, NodeType
 
     /**
      * Package private constructor
-     * <p/>
+     * <p>
      * Creates a valid node type instance. We assume that the node type
      * definition is valid and all referenced node types (supertypes, required
      * node types etc.) do exist and are valid.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeManagerImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeManagerImpl.java
index c32ce44..ab8ea6b 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeManagerImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeManagerImpl.java
@@ -126,6 +126,13 @@ public class NodeTypeManagerImpl extends AbstractNodeTypeManager
     }
 
     /**
+     * Disposes this node type manager.
+     */
+    public void dispose() {
+        context.getNodeTypeRegistry().removeListener(this);
+    }
+
+    /**
      * @return the root node definition
      */
     public NodeDefinitionImpl getRootNodeDefinition() {
@@ -516,20 +523,20 @@ public class NodeTypeManagerImpl extends AbstractNodeTypeManager
      * <code>NodeTypeDefinition</code> objects. This method is used to register
      * or update a set of node types with mutual dependencies. Returns an
      * iterator over the resulting <code>NodeType</code> objects.
-     * <p/>
+     * <p>
      * The effect of the method is "all or nothing"; if an error occurs, no node
      * types are registered or updated.
-     * <p/>
+     * <p>
      * Throws an <code>InvalidNodeTypeDefinitionException</code> if a
      * <code>NodeTypeDefinition</code> within the <code>Collection</code> is
      * invalid or if the <code>Collection</code> contains an object of a type
      * other than <code>NodeTypeDefinition</code>.
-     * <p/>
+     * <p>
      * Throws a <code>NodeTypeExistsException</code> if <code>allowUpdate</code>
      * is <code>false</code> and a <code>NodeTypeDefinition</code> within the
      * <code>Collection</code> specifies a node type name that is already
      * registered.
-     * <p/>
+     * <p>
      * Throws an <code>UnsupportedRepositoryOperationException</code> if this
      * implementation does not support node type registration.
      *
@@ -598,9 +605,9 @@ public class NodeTypeManagerImpl extends AbstractNodeTypeManager
 
     /**
      * Unregisters the specified set of node types. Used to unregister a set of node types with mutual dependencies.
-     * <p/>
+     * <p>
      * Throws a <code>NoSuchNodeTypeException</code> if one of the names listed is not a registered node type.
-     * <p/>
+     * <p>
      * Throws an <code>UnsupportedRepositoryOperationException</code>
      * if this implementation does not support node type registration.
      *
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeRegistry.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeRegistry.java
index 6a170e3..8d4de2b 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeRegistry.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/NodeTypeRegistry.java
@@ -77,6 +77,20 @@ public class NodeTypeRegistry implements NodeTypeEventListener {
             "/nodetypes/custom_nodetypes.xml";
 
     /**
+     * Feature flag for the unfortunate behavior in Jackrabbit 2.1 and 2.2
+     * where the exception from {@link #checkForReferencesInContent(Name)}
+     * was never thrown because of a mistaken commit for
+     * <a href="https://issues.apache.org/jira/browse/JCR-2587">JCR-2587</a>.
+     * Setting this flag to <code>true</code> (the default value comes from
+     * the "disableCheckForReferencesInContentException" system property)
+     * will disable the exception thrown by default by the method.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/JCR-3223">JCR-3223</a>
+     */
+    public static volatile boolean disableCheckForReferencesInContentException =
+            Boolean.getBoolean("disableCheckForReferencesInContentException");
+
+    /**
      * resource holding custom node type definitions which are represented as
      * nodes in the repository; it is needed in order to make the registrations
      * persistent.
@@ -129,7 +143,7 @@ public class NodeTypeRegistry implements NodeTypeEventListener {
      * Validates the <code>NodeTypeDef</code> and returns
      * an  <code>EffectiveNodeType</code> object representing the newly
      * registered node type.
-     * <p/>
+     * <p>
      * The validation includes the following checks:
      * <ul>
      * <li>Supertypes must exist and be registered</li>
@@ -194,7 +208,7 @@ public class NodeTypeRegistry implements NodeTypeEventListener {
      * Same as <code>{@link #registerNodeType(QNodeTypeDefinition)}</code> except
      * that a collection of <code>NodeTypeDef</code>s is registered instead of
      * just one.
-     * <p/>
+     * <p>
      * This method can be used to register a set of node types that have
      * dependencies on each other.
      *
@@ -223,7 +237,7 @@ public class NodeTypeRegistry implements NodeTypeEventListener {
         synchronized (this) {
 
             // validate and register new node type definitions
-            internalRegister(ntDefs);
+            internalRegister(ntDefs, external);
             // persist new node type definitions
             for (QNodeTypeDefinition ntDef: ntDefs) {
                 customNTDefs.add(ntDef);
@@ -247,7 +261,7 @@ public class NodeTypeRegistry implements NodeTypeEventListener {
     /**
      * Same as <code>{@link #unregisterNodeType(Name)}</code> except
      * that a set of node types is unregistered instead of just one.
-     * <p/>
+     * <p>
      * This method can be used to unregister a set of node types that depend on
      * each other.
      *
@@ -415,40 +429,11 @@ public class NodeTypeRegistry implements NodeTypeEventListener {
                 return getEffectiveNodeType(name);
             }
 
-            if (!diff.isTrivial()) {
-
-                // TODO Implement checkForConflictingContent()
-                // make sure existing content would not conflict
-                // with new node type definition
-                //checkForConflictingContent(ntd);
-                //
-                // unregister old node type definition
-                //internalUnregister(name);
-                // register new definition
-                //EffectiveNodeType entNew = internalRegister(ntd);
-                //
-                // persist modified node type definitions
-                //customNTDefs.remove(name);
-                //customNTDefs.add(ntd);
-                //persistCustomNodeTypeDefs(customNTDefs);
-                //
-                // notify listeners
-                //notifyReRegistered(name);
-                //return entNew;
-
-                String message =
-                    "The following node type change contains non-trivial changes."
-                    + "Up until now only trivial changes are supported."
-                    + " (see javadoc for "
-                    + NodeTypeDefDiff.class.getName()
-                    + "):\n" + diff.toString();
-                throw new RepositoryException(message);
-            }
+            // make sure existing content would not conflict
+            // with new node type definition
+            checkForConflictingContent(ntd, diff);
 
             /**
-             * the change is trivial and has no effect on current content
-             * (e.g. that would be the case when non-mandatory properties had
-             * been added);
              * re-register node type definition and update caches &
              * notify listeners on re-registration
              */
@@ -761,7 +746,7 @@ public class NodeTypeRegistry implements NodeTypeEventListener {
             loadBuiltInNodeTypeDefs(builtInNTDefs);
 
             // register built-in node types
-            internalRegister(builtInNTDefs.all(), true);
+            internalRegister(builtInNTDefs.all(), false, true);
         } catch (InvalidNodeTypeDefException intde) {
             String error =
                     "internal error: invalid built-in node type definition stored in "
@@ -778,7 +763,7 @@ public class NodeTypeRegistry implements NodeTypeEventListener {
 
         // validate & register custom node types
         try {
-            internalRegister(customNTDefs.all());
+            internalRegister(customNTDefs.all(), false);
         } catch (InvalidNodeTypeDefException intde) {
             String error =
                     "internal error: invalid custom node type definition stored in "
@@ -790,7 +775,7 @@ public class NodeTypeRegistry implements NodeTypeEventListener {
 
     /**
      * Loads the built-in node type definitions into the given <code>store</code>.
-     * <p/>
+     * <p>
      * This method may be overridden by extensions of this class; It must
      * only be called once and only from within the constructor though.
      *
@@ -827,7 +812,7 @@ public class NodeTypeRegistry implements NodeTypeEventListener {
 
     /**
      * Loads the custom node type definitions into the given <code>store</code>.
-     * <p/>
+     * <p>
      * This method may be overridden by extensions of this class; It must
      * only be called once and only from within the constructor though.
      *
@@ -915,10 +900,10 @@ public class NodeTypeRegistry implements NodeTypeEventListener {
     /**
      * Checks whether there is existing content that would conflict with the
      * given node type definition.
-     * <p/>
+     * <p>
      * This method is not implemented yet and always throws a
      * <code>RepositoryException</code>.
-     * <p/>
+     * <p>
      * TODO
      * <ol>
      * <li>apply deep locks on root nodes in every workspace or alternatively
@@ -931,33 +916,50 @@ public class NodeTypeRegistry implements NodeTypeEventListener {
      * <li>apply and persist changes to affected nodes (e.g. update
      * definition id's, etc.)
      * </ul>
-     * <p/>
+     * <p>
      * the above checks/actions are absolutely necessary in order to
      * guarantee integrity of repository content.
      *
+     *
      * @param ntd The node type definition replacing the former node type
      *            definition of the same name.
+     * @param diff
      * @throws RepositoryException If there is conflicting content or if the
      *                             check failed for some other reason.
      */
-    protected void checkForConflictingContent(QNodeTypeDefinition ntd)
+    protected void checkForConflictingContent(QNodeTypeDefinition ntd, final NodeTypeDefDiff diff)
             throws RepositoryException {
+
+        if (!diff.isTrivial()) {
+            /**
+             * collect names of node types that have dependencies on the given
+             * node type
+             */
+            //Set dependentNTs = getDependentNodeTypes(ntd.getName());
+
+            String message =
+                    "The following node type change contains non-trivial changes."
+                            + "Up until now only trivial changes are supported."
+                            + " (see javadoc for "
+                            + NodeTypeDefDiff.class.getName()
+                            + "):\n" + diff.toString();
+            throw new RepositoryException(message);
+        }
+
         /**
-         * collect names of node types that have dependencies on the given
-         * node type
+         * the change is trivial and has no effect on current content
+         * (e.g. that would be the case when non-mandatory properties had
+         * been added);
          */
-        //Set dependentNTs = getDependentNodeTypes(ntd.getName());
-
-        throw new RepositoryException("not yet implemented");
     }
 
     /**
      * Checks whether there is existing content that directly or indirectly
      * refers to the specified node type.
-     * <p/>
+     * <p>
      * This method is not implemented yet and always throws a
      * <code>RepositoryException</code>.
-     * <p/>
+     * <p>
      * TODO:
      * <ol>
      * <li>apply deep locks on root nodes in every workspace or alternatively
@@ -967,7 +969,7 @@ public class NodeTypeRegistry implements NodeTypeEventListener {
      * <li>remove the node type if it is not currently referenced, otherwise
      * throw exception
      * </ul>
-     * <p/>
+     * <p>
      * the above checks are absolutely necessary in order to guarantee
      * integrity of repository content.
      *
@@ -978,7 +980,18 @@ public class NodeTypeRegistry implements NodeTypeEventListener {
      */
     protected void checkForReferencesInContent(Name nodeTypeName)
             throws RepositoryException {
-        throw new RepositoryException("not yet implemented");
+        if (!disableCheckForReferencesInContentException) {
+            throw new RepositoryException(
+                    "The check for the existence of content using the"
+                    + " given node type is not yet implemented, so to"
+                    + " guarantee repository consistency the request to"
+                    + " unregister the type is denied. Contributions to"
+                    + " implement this feature would be welcome! To restore"
+                    + " the broken behavior of previous Jackrabbit versions"
+                    + " where this check was simply skipped, please set the"
+                    + " disableCheckForReferencesInContentException system"
+                    + " property to true.");
+        }
     }
 
     //-------------------------------------------------------< implementation >
@@ -1235,7 +1248,7 @@ public class NodeTypeRegistry implements NodeTypeEventListener {
      * Validates and registers the specified collection of <code>NodeTypeDef</code>
      * objects. An <code>InvalidNodeTypeDefException</code> is thrown if the
      * validation of any of the contained <code>NodeTypeDef</code> objects fails.
-     * <p/>
+     * <p>
      * Note that in the case an exception is thrown no node type will be
      * eventually registered.
      *
@@ -1244,13 +1257,13 @@ public class NodeTypeRegistry implements NodeTypeEventListener {
      * @throws RepositoryException if an error occurs
      * @see #registerNodeType
      */
-    private void internalRegister(Collection<QNodeTypeDefinition> ntDefs)
+    private void internalRegister(Collection<QNodeTypeDefinition> ntDefs, boolean external)
             throws InvalidNodeTypeDefException, RepositoryException {
-        internalRegister(ntDefs, false);
+        internalRegister(ntDefs, external, false);
     }
 
     /**
-     * Same as {@link #internalRegister(java.util.Collection)} except for the
+     * Same as {@link #internalRegister(java.util.Collection, boolean)} except for the
      * additional <code>lenient</code> parameter which governs whether
      * validation can be lenient (e.g. for built-in node types) or has to be
      * strict (such as in the case of custom node types). This differentiation
@@ -1259,7 +1272,7 @@ public class NodeTypeRegistry implements NodeTypeEventListener {
      * that can be exposed in a property definition because it is
      * system-generated (such as jcr:primaryType in nt:base).
      */
-    private void internalRegister(Collection<QNodeTypeDefinition> ntDefs, boolean lenient)
+    private void internalRegister(Collection<QNodeTypeDefinition> ntDefs, boolean external, boolean lenient)
             throws InvalidNodeTypeDefException, RepositoryException {
 
         // need a list/collection that can be modified
@@ -1272,11 +1285,8 @@ public class NodeTypeRegistry implements NodeTypeEventListener {
         // and do some preliminary checks
         for (QNodeTypeDefinition ntd : defs) {
             Name name = ntd.getName();
-            if (name != null && tmpNTDefCache.containsKey(name)) {
-                String msg = name + " already exists";
-                if (tmpNTDefCache.containsKey(name)) {
-                    msg += " locally";
-                }
+            if (!external && name != null && tmpNTDefCache.containsKey(name)) {
+                String msg = name + " already exists locally";
                 log.debug(msg);
                 throw new InvalidNodeTypeDefException(msg);
             }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/virtual/VirtualNodeTypeStateProvider.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/virtual/VirtualNodeTypeStateProvider.java
index a291c3c..5cc2261 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/virtual/VirtualNodeTypeStateProvider.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/nodetype/virtual/VirtualNodeTypeStateProvider.java
@@ -69,7 +69,7 @@ public class VirtualNodeTypeStateProvider extends AbstractVISProvider {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * currently we have no dynamic ones, we just recreate the entire nodetypes tree
      */
     protected VirtualNodeState createRootNodeState() throws RepositoryException {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventConsumer.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventConsumer.java
index 5ccc479..e80c08a 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventConsumer.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventConsumer.java
@@ -31,6 +31,7 @@ import javax.jcr.observation.EventListener;
 
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.id.ItemId;
+import org.apache.jackrabbit.core.security.authorization.Permission;
 import org.apache.jackrabbit.core.state.ItemState;
 import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.PathFactory;
@@ -42,7 +43,7 @@ import org.slf4j.LoggerFactory;
  * The <code>EventConsumer</code> class combines the {@link
  * javax.jcr.observation.EventListener} with the implementation of specified
  * filter for the listener: {@link EventFilter}.
- * <p/>
+ * <p>
  * Collections of {@link EventState} objects will be dispatched to {@link
  * #consumeEvents}.
  */
@@ -259,7 +260,7 @@ class EventConsumer {
     /**
      * Returns <code>true</code> if this <code>EventConsumer</code> is equal to
      * some other object, <code>false</code> otherwise.
-     * <p/>
+     * <p>
      * Two <code>EventConsumer</code>s are considered equal if they refer to the
      * same <code>Session</code> and the <code>EventListener</code>s they
      * reference are equal. Note that the <code>EventFilter</code> is ignored in
@@ -303,6 +304,6 @@ class EventConsumer {
      */
     private boolean canRead(EventState eventState) throws RepositoryException {
         Path targetPath = pathFactory.create(eventState.getParentPath(), eventState.getChildRelPath().getName(), eventState.getChildRelPath().getNormalizedIndex(), true);
-        return session.getAccessManager().canRead(targetPath, null);
+        return session.getAccessManager().isGranted(targetPath, Permission.READ);
     }
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventFilter.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventFilter.java
index e6cad71..b468fd2 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventFilter.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventFilter.java
@@ -16,17 +16,19 @@
  */
 package org.apache.jackrabbit.core.observation;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Set;
 
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.observation.Event;
+
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.nodetype.NodeTypeImpl;
 import org.apache.jackrabbit.spi.Path;
 
-import javax.jcr.RepositoryException;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.observation.Event;
-
 /**
  * The <code>EventFilter</code> class implements the filter logic based
  * on the session's access rights and the specified filter rules.
@@ -47,9 +49,9 @@ public class EventFilter {
     private final long eventTypes;
 
     /**
-     * Only allow Items with the specified <code>path</code>
+     * Only allow Items with the specified <code>paths</code>
      */
-    private final Path path;
+    private final List<Path> paths;
 
     /**
      * If <code>isDeep</code> is <code>true</code> also Items under <code>absPath</code>
@@ -74,13 +76,25 @@ public class EventFilter {
     private final boolean noLocal;
 
     /**
+     * If <code>noExternal</code> is true this filter will block events from
+     * other cluster nodes.
+     */
+    private final boolean noExternal;
+
+    /**
+     * If <code>noInternal</code> is true this filter will block events from
+     * this cluster nodes.
+     */
+    private final boolean noInternal;
+
+    /**
      * Creates a new <code>EventFilter</code> instance.
      *
      * @param session    the <code>Session</code> that registered the {@link
      *                   javax.jcr.observation.EventListener}.
      * @param eventTypes only allow specified {@link javax.jcr.observation.Event} types.
-     * @param path       only allow {@link javax.jcr.Item} with
-     *                   <code>path</code>.
+     * @param paths      only allow {@link javax.jcr.Item} with a path in
+     *                   <code>paths</code>.
      * @param isDeep     if <code>true</code> also allow events for {@link
      *                   javax.jcr.Item}s below <code>absPath</code>.
      * @param ids        only allow events for {@link javax.jcr.Node}s with
@@ -92,20 +106,28 @@ public class EventFilter {
      * @param noLocal    if <code>true</code> no events are allowed that were
      *                   created from changes related to the <code>Session</code>
      *                   that registered the {@link javax.jcr.observation.EventListener}.
+     * @param noExternal if <code>true</code> no events are allowed that were
+     *                   created from changes on an external cluster node.
+     * @param noInternal if <code>true</code> no events are allowed that were
+     *                   created from changes on the local cluster node.
      */
     EventFilter(SessionImpl session,
                 long eventTypes,
-                Path path,
+                List<Path> paths,
                 boolean isDeep,
                 NodeId[] ids,
                 NodeTypeImpl[] nodeTypes,
-                boolean noLocal) {
+                boolean noLocal,
+                boolean noExternal,
+                boolean noInternal) {
         this.session = session;
         this.eventTypes = eventTypes;
-        this.path = path;
+        this.paths = paths;
         this.isDeep = isDeep;
         this.ids = ids;
         this.noLocal = noLocal;
+        this.noExternal = noExternal;
+        this.noInternal = noInternal;
         this.nodeTypes = nodeTypes;
     }
 
@@ -133,6 +155,14 @@ public class EventFilter {
             return true;
         }
 
+        if (noExternal && eventState.isExternal()) {
+            return true;
+        }
+
+        if (noInternal && !eventState.isExternal()) {
+            return true;
+        }
+
         // UUIDs, types, and paths do not need to match for persist
         if (eventState.getType() == Event.PERSIST) {
             return false;
@@ -166,11 +196,14 @@ public class EventFilter {
             }
         }
 
-        // finally check path
+        // finally check paths
         Path eventPath = eventState.getParentPath();
-        boolean match = eventPath.equals(path);
-        if (!match && isDeep) {
-            match = eventPath.isDescendantOf(path);
+        boolean match = false;
+        for (Path path : paths) {
+            if (eventPath.equals(path) || isDeep && eventPath.isDescendantOf(path)) {
+                match = true;
+                break;
+            }
         }
 
         return !match;
@@ -186,7 +219,7 @@ public class EventFilter {
          * Creates a new <code>BlockAllFilter</code>.
          */
         BlockAllFilter() {
-            super(null, 0, null, true, null, null, true);
+            super(null, 0, Collections.<Path>emptyList(), true, null, null, true, true, true);
         }
 
         /**
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventImpl.java
index 386144e..9b71875 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventImpl.java
@@ -105,7 +105,7 @@ public final class EventImpl implements JackrabbitEvent, AdditionalEventInfo, Ev
      */
     public String getPath() throws RepositoryException {
         Path p = getQPath();
-        return p != null ? session.getJCRPath(getQPath()) : null;
+        return p != null ? session.getJCRPath(p) : null;
     }
 
     /**
@@ -303,7 +303,7 @@ public final class EventImpl implements JackrabbitEvent, AdditionalEventInfo, Ev
     /**
      * Returns <code>true</code> if this <code>Event</code> is equal to another
      * object.
-     * <p/>
+     * <p>
      * Two <code>Event</code> instances are equal if their respective
      * <code>EventState</code> instances are equal and both <code>Event</code>
      * instances are intended for the same <code>Session</code> that registerd
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventJournalImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventJournalImpl.java
index f858ef4..32d8185 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventJournalImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventJournalImpl.java
@@ -135,7 +135,7 @@ public class EventJournalImpl implements EventJournal {
         // get skip map for this journal
         SortedMap<Long, Long> skipMap = getSkipMap();
         synchronized (skipMap) {
-            SortedMap<Long, Long> head = skipMap.headMap(new Long(time));
+            SortedMap<Long, Long> head = skipMap.headMap(new Long(date));
             if (!head.isEmpty()) {
                 eventBundleBuffer.clear();
                 lastRevision = head.get(head.lastKey());
@@ -409,7 +409,6 @@ public class EventJournalImpl implements EventJournal {
          * @param eventStates the {@link EventState}s that belong to this bundle.
          * @param timestamp the timestamp when the events were created.
          * @param userData the user data associated with this event.
-         * @param filter the event filter.
          */
         private EventBundle(
                 List<EventState> eventStates, long timestamp, String userData) {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventState.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventState.java
index 31b0ae3..87d3ec6 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventState.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventState.java
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.core.observation;
 
+import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl;
 import org.apache.jackrabbit.core.id.ItemId;
 import org.apache.jackrabbit.core.id.PropertyId;
@@ -24,15 +25,24 @@ import org.apache.jackrabbit.core.value.InternalValue;
 import org.apache.jackrabbit.core.state.ItemStateException;
 import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.conversion.CachingPathResolver;
+import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException;
+import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
+import org.apache.jackrabbit.spi.commons.conversion.ParsingPathResolver;
+import org.apache.jackrabbit.spi.commons.conversion.PathResolver;
+import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
+
 import javax.jcr.observation.Event;
 import org.slf4j.LoggerFactory;
 import org.slf4j.Logger;
 
+import javax.jcr.NamespaceException;
 import javax.jcr.Session;
 import javax.jcr.RepositoryException;
 import javax.jcr.nodetype.NoSuchNodeTypeException;
 import javax.jcr.nodetype.NodeType;
 
+import java.util.List;
 import java.util.Set;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -52,6 +62,11 @@ public class EventState {
     private static final Logger log = LoggerFactory.getLogger(EventState.class);
 
     /**
+     * The caching path resolver.
+     */
+    private static CachingPathResolver cachingPathResolver;
+
+    /**
      * The key <code>srcAbsPath</code> in the info map.
      */
     static final String SRC_ABS_PATH = "srcAbsPath";
@@ -865,4 +880,83 @@ public class EventState {
     private static InternalValue createValue(Path path) {
         return InternalValue.create(path);
     }
+
+    /**
+     * Get the longest common path of all event state paths.
+     *
+     * @param events The list of EventState
+     * @param session The associated session; it can be null
+     * @return the longest common path
+     */
+    public static String getCommonPath(List<EventState> events, SessionImpl session) {
+        String common = null;
+        try {
+            for (int i = 0; i < events.size(); i++) {
+                EventState state = events.get(i);
+                Path parentPath = state.getParentPath();
+                String s;
+                if (session == null) {
+                    s = getJCRPath(parentPath);
+                } else {
+                    s = session.getJCRPath(parentPath);
+                }
+
+                if (common == null) {
+                    common = s;
+                } else if (!common.equals(s)) {
+
+                    // Assign the shorter path to common.
+                    if (s.length() < common.length()) {
+                        String temp = common;
+                        common = s;
+                        s = temp;
+                    }
+
+                    // Find the real common.
+                    while (!s.startsWith(common)) {
+                        int idx = s.lastIndexOf('/');
+                        if (idx < 0) {
+                            break;
+                        }
+                        common = s.substring(0, idx + 1);
+                    }
+                }
+            }
+        } catch (NamespaceException e) {
+            log.debug("Problem in retrieving JCR path", e);
+        }
+        return common;
+    }
+
+    private static String getJCRPath(Path path) {
+ 
+        setupCachingPathResolver();
+
+        String jcrPath;
+        try {
+            jcrPath = cachingPathResolver.getJCRPath(path);
+        } catch (NamespaceException e) {
+            jcrPath = "";
+            log.debug("Problem in retrieving JCR path", e);
+        }
+        return jcrPath;
+    }
+
+    private static void setupCachingPathResolver() {
+        if (cachingPathResolver != null) {
+            return;
+        }
+
+        PathResolver pathResolver = new ParsingPathResolver(PathFactoryImpl.getInstance(), new NameResolver() {
+            public Name getQName(String name) throws IllegalNameException, NamespaceException {
+                return null;
+            }
+
+            public String getJCRName(Name name) throws NamespaceException {
+                return name.getLocalName();
+            }
+        });
+
+        cachingPathResolver = new CachingPathResolver(pathResolver);
+    }
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java
index f6add76..5f795e0 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/EventStateCollection.java
@@ -50,7 +50,7 @@ import java.util.Collections;
  * The <code>EventStateCollection</code> class implements how {@link EventState}
  * objects are created based on the {@link org.apache.jackrabbit.core.state.ItemState}s
  * passed to the {@link #createEventStates} method.
- * <p/>
+ * <p>
  * The basic sequence of method calls is:
  * <ul>
  * <li>{@link #createEventStates} or {@link #addAll} to create or add event
@@ -101,7 +101,7 @@ public final class EventStateCollection {
 
     /**
      * Creates a new empty <code>EventStateCollection</code>.
-     * <p/>
+     * <p>
      * Because the item state manager in {@link #createEventStates} may represent
      * only a subset of the over all item state hierarchy, this constructor
      * also takes a path prefix argument. If non <code>null</code> all events
@@ -794,26 +794,6 @@ public final class EventStateCollection {
      * @return the longest common path
      */
     public String getCommonPath() {
-        String common = null;
-        try {
-            for (int i = 0; i < events.size(); i++) {
-                EventState state = events.get(i);
-                String s = session.getJCRPath(state.getParentPath());
-                if (common == null) {
-                    common = s;
-                } else if (!common.equals(s)) {
-                    while (!s.startsWith(common)) {
-                        int idx = s.lastIndexOf('/');
-                        if (idx < 0) {
-                            break;
-                        }
-                        common = s.substring(0, idx);
-                    }
-                }
-            }
-        } catch (NamespaceException e) {
-            // ignore
-        }
-        return common;
+        return EventState.getCommonPath(events, session);
     }
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/ObservationDispatcher.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/ObservationDispatcher.java
index 791f67e..f527717 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/ObservationDispatcher.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/ObservationDispatcher.java
@@ -160,8 +160,9 @@ public final class ObservationDispatcher extends EventDispatcher
                 try {
                     c.consumeEvents(action.getEventStates());
                 } catch (Throwable t) {
-                    log.warn("EventConsumer threw exception: " + t.toString());
-                    log.debug("Stacktrace: ", t);
+                    log.warn("EventConsumer " +
+                            c.getEventListener().getClass().getName() +
+                            " threw exception", t);
                     // move on to the next consumer
                 }
             }
@@ -172,7 +173,7 @@ public final class ObservationDispatcher extends EventDispatcher
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Gives this observation manager the opportunity to
      * prepare the events for dispatching.
      */
@@ -199,11 +200,21 @@ public final class ObservationDispatcher extends EventDispatcher
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Dispatches the {@link EventStateCollection events} to all
      * registered {@link javax.jcr.observation.EventListener}s.
      */
     void dispatchEvents(EventStateCollection events) {
+        // JCR-3426: log warning when changes are done
+        // with the notification thread
+        if (Thread.currentThread() == notificationThread) {
+            log.warn("Save call with event notification thread detected. This " +
+                    "may lead to a growing event queue. Enable debug log to " +
+                    "see the stack trace with the class calling save().");
+            if (log.isDebugEnabled()) {
+                log.debug("Stack trace:", new Exception());
+            }
+        }
         // notify synchronous listeners
         Set<EventConsumer> synchronous = getSynchronousConsumers();
         if (log.isDebugEnabled()) {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/ObservationManagerImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/ObservationManagerImpl.java
index 66df2c6..612db75 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/ObservationManagerImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/ObservationManagerImpl.java
@@ -16,15 +16,10 @@
  */
 package org.apache.jackrabbit.core.observation;
 
-import org.apache.jackrabbit.core.SessionImpl;
-import org.apache.jackrabbit.core.id.NodeId;
-import org.apache.jackrabbit.core.cluster.ClusterNode;
-import org.apache.jackrabbit.core.nodetype.NodeTypeImpl;
-import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl;
-import org.apache.jackrabbit.spi.commons.conversion.NameException;
-import org.apache.jackrabbit.spi.Path;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 
 import javax.jcr.AccessDeniedException;
 import javax.jcr.RepositoryException;
@@ -34,12 +29,25 @@ import javax.jcr.observation.EventListener;
 import javax.jcr.observation.EventListenerIterator;
 import javax.jcr.observation.ObservationManager;
 
+import org.apache.jackrabbit.api.observation.JackrabbitEventFilter;
+import org.apache.jackrabbit.api.observation.JackrabbitObservationManager;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.cluster.ClusterNode;
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.nodetype.NodeTypeImpl;
+import org.apache.jackrabbit.core.nodetype.NodeTypeManagerImpl;
+import org.apache.jackrabbit.spi.Path;
+import org.apache.jackrabbit.spi.commons.conversion.NameException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 /**
  * Each <code>Session</code> instance has its own <code>ObservationManager</code>
  * instance. The class <code>SessionLocalObservationManager</code> implements
  * this behaviour.
  */
-public class ObservationManagerImpl implements ObservationManager, EventStateCollectionFactory {
+public class ObservationManagerImpl implements EventStateCollectionFactory,
+        JackrabbitObservationManager {
 
     /**
      * The logger instance of this class
@@ -110,12 +118,34 @@ public class ObservationManagerImpl implements ObservationManager, EventStateCol
             throws RepositoryException {
 
         // create filter
-        EventFilter filter = createEventFilter(eventTypes, absPath,
-                isDeep, uuid, nodeTypeName, noLocal);
+        EventFilter filter = createEventFilter(eventTypes, Collections.singletonList(absPath),
+                isDeep, uuid, nodeTypeName, noLocal, false, false);
 
         dispatcher.addConsumer(new EventConsumer(session, listener, filter));
     }
 
+    @Override
+    public void addEventListener(EventListener listener, JackrabbitEventFilter filter)
+            throws RepositoryException {
+
+        String[] excludedPaths = filter.getExcludedPaths();
+        if (excludedPaths.length > 0) {
+            log.warn("JackrabbitEventFilter excludedPaths is not implemented and will be ignored: {}",
+                    Arrays.toString(excludedPaths));
+        }
+
+        List<String> absPaths = new ArrayList<String>(Arrays.asList(filter.getAdditionalPaths()));
+        if (filter.getAbsPath() != null) {
+            absPaths.add(filter.getAbsPath());
+        }
+
+        EventFilter f = createEventFilter(filter.getEventTypes(), absPaths,
+                filter.getIsDeep(), filter.getIdentifiers(), filter.getNodeTypes(),
+                filter.getNoLocal(), filter.getNoExternal(), filter.getNoInternal());
+
+        dispatcher.addConsumer(new EventConsumer(session, listener, f));
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -172,20 +202,24 @@ public class ObservationManagerImpl implements ObservationManager, EventStateCol
      * Creates a new event filter with the given restrictions.
      *
      * @param eventTypes A combination of one or more event type constants encoded as a bitmask.
-     * @param absPath an absolute path.
+     * @param absPaths absolute paths.
      * @param isDeep a <code>boolean</code>.
      * @param uuid array of UUIDs.
      * @param nodeTypeName array of node type names.
      * @param noLocal a <code>boolean</code>.
+     * @param noExternal a <code>boolean</code>.
+     * @param noInternal a <code>boolean</code>.
      * @return the event filter with the given restrictions.
      * @throws RepositoryException if an error occurs.
      */
     public EventFilter createEventFilter(int eventTypes,
-                                         String absPath,
+                                         List<String> absPaths,
                                          boolean isDeep,
                                          String[] uuid,
                                          String[] nodeTypeName,
-                                         boolean noLocal)
+                                         boolean noLocal,
+                                         boolean noExternal,
+                                         boolean noInternal)
             throws RepositoryException {
         // create NodeType instances from names
         NodeTypeImpl[] nodeTypes;
@@ -199,17 +233,20 @@ public class ObservationManagerImpl implements ObservationManager, EventStateCol
             }
         }
 
-        Path path;
-        try {
-            path = session.getQPath(absPath).getNormalizedPath();
-        } catch (NameException e) {
-            String msg = "invalid path syntax: " + absPath;
-            log.debug(msg);
-            throw new RepositoryException(msg, e);
-        }
-        if (!path.isAbsolute()) {
-            throw new RepositoryException("absPath must be absolute");
-        }
+        List<Path> paths = new ArrayList<Path>();
+        for (String absPath : absPaths) {
+            try {
+                Path normalizedPath = session.getQPath(absPath).getNormalizedPath();
+                if (!normalizedPath.isAbsolute()) {
+                    throw new RepositoryException("absPath must be absolute");
+                }
+                paths.add(normalizedPath);
+            } catch (NameException e) {
+                String msg = "invalid path syntax: " + absPath;
+                log.debug(msg);
+                throw new RepositoryException(msg, e);
+
+        }            }
         NodeId[] ids = null;
         if (uuid != null) {
             ids = new NodeId[uuid.length];
@@ -219,7 +256,7 @@ public class ObservationManagerImpl implements ObservationManager, EventStateCol
         }
         // create filter
         return new EventFilter(
-                session, eventTypes, path, isDeep, ids, nodeTypes, noLocal);
+                session, eventTypes, paths, isDeep, ids, nodeTypes, noLocal, noExternal, noInternal);
     }
 
     /**
@@ -251,7 +288,7 @@ public class ObservationManagerImpl implements ObservationManager, EventStateCol
         }
 
         EventFilter filter = createEventFilter(
-                eventTypes, absPath, isDeep, uuid, nodeTypeName, false);
+                eventTypes, Collections.singletonList(absPath), isDeep, uuid, nodeTypeName, false, false, false);
         return new EventJournalImpl(
                 filter, clusterNode.getJournal(), clusterNode.getId(), session);
     }
@@ -272,7 +309,7 @@ public class ObservationManagerImpl implements ObservationManager, EventStateCol
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Creates an <code>EventStateCollection</code> tied to the session
      * which is attached to this <code>ObservationManager</code> instance.
      */
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/SynchronousEventListener.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/SynchronousEventListener.java
index b524462..fdf2cea 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/SynchronousEventListener.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/SynchronousEventListener.java
@@ -26,7 +26,7 @@ import javax.jcr.observation.EventListener;
  * the call to {@link javax.jcr.Item#save()} returns. In contrast, a regular
  * {@link javax.jcr.observation.EventListener} might be called after
  * <code>save()</code> returns.
- * <p/>
+ * <p>
  * <b>Important note</b>: an implementation of {@link SynchronousEventListener}
  * <b>must not</b> modify content with the thread that calls {@link
  * #onEvent(EventIterator)} otherwise inconsistencies may occur.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/IterablePersistenceManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/IterablePersistenceManager.java
index 77c3757..73d63dd 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/IterablePersistenceManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/IterablePersistenceManager.java
@@ -16,32 +16,54 @@
  */
 package org.apache.jackrabbit.core.persistence;
 
+import java.util.List;
+import java.util.Map;
+
 import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.persistence.util.NodeInfo;
 import org.apache.jackrabbit.core.state.ItemStateException;
 
 import javax.jcr.RepositoryException;
 
 /**
- * The iterable persistence manager can return the list of node ids that are stored.
+ * The iterable persistence manager can return the list of {@link NodeId}s and
+ * {@link NodeInfo}s that are stored.
  * Possible applications are backup, migration (copying a workspace or repository),
- * and data store garbage collection.
+ * data store garbage collection, and consistency checking.
  */
 public interface IterablePersistenceManager extends PersistenceManager {
 
     /**
      * Get all node ids.
      * A typical application will call this method multiple times, where 'after'
-     * is the last row read. The maxCount parameter defines the maximum number of
+     * is the last row read previously. The maxCount parameter defines the maximum number of
      * node ids returned, 0 meaning no limit. The order of the node ids is specific for the
      * given persistent manager. Items that are added concurrently may not be included.
      *
      * @param after the lower limit, or null for no limit.
      * @param maxCount the maximum number of node ids to return, or 0 for no limit.
-     * @return an iterator of all bundles.
+     * @return a list of all node ids.
+     * @throws ItemStateException if an error while loading occurs.
+     * @throws RepositoryException if a repository exception occurs.
+     */
+    List<NodeId> getAllNodeIds(NodeId after, int maxCount)
+            throws ItemStateException, RepositoryException;
+
+
+    /**
+     * Get all {@link NodeInfo}s.
+     * A typical application will call this method multiple time, where 'after'
+     * is the last row read previously. The maxCount parameter defines the maximum number of
+     * node ids returned, 0 meaning no limit. The order of the node ids is specific for the
+     * given persistence manager. Items that are added concurrently may not be included.
+     *
+     * @param after the lower limit, or null for no limit.
+     * @param maxCount the maximum number of node infos to return, or 0 for no limit.
+     * @return a list of all node infos.
      * @throws ItemStateException if an error while loading occurs.
-     * @throws RepositoryException if a repository exception occurs
+     * @throws RepositoryException if a repository exception occurs.
      */
-    Iterable<NodeId> getAllNodeIds(NodeId after, int maxCount)
+    Map<NodeId, NodeInfo> getAllNodeInfos(NodeId after, int maxCount)
             throws ItemStateException, RepositoryException;
 
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/PMContext.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/PMContext.java
index d890eb1..272bae8 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/PMContext.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/PMContext.java
@@ -24,7 +24,7 @@ import org.apache.jackrabbit.core.data.DataStore;
 import org.apache.jackrabbit.core.fs.FileSystem;
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
-import org.apache.jackrabbit.core.stats.RepositoryStatisticsImpl;
+import org.apache.jackrabbit.stats.RepositoryStatisticsImpl;
 
 /**
  * A <code>PMContext</code> is used to provide context information for a
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java
index 1672cba..70a6571 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/AbstractBundlePersistenceManager.java
@@ -22,15 +22,19 @@ import static org.apache.jackrabbit.spi.commons.name.NameConstants.JCR_UUID;
 
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicLong;
 
 import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
 
 import org.apache.jackrabbit.api.stats.RepositoryStatistics;
 import org.apache.jackrabbit.core.cache.Cache;
 import org.apache.jackrabbit.core.cache.CacheAccessListener;
 import org.apache.jackrabbit.core.cache.ConcurrentCache;
+import org.apache.jackrabbit.core.cluster.UpdateEventChannel;
 import org.apache.jackrabbit.core.fs.FileSystem;
 import org.apache.jackrabbit.core.fs.FileSystemResource;
 import org.apache.jackrabbit.core.id.ItemId;
@@ -40,8 +44,12 @@ import org.apache.jackrabbit.core.persistence.CachingPersistenceManager;
 import org.apache.jackrabbit.core.persistence.IterablePersistenceManager;
 import org.apache.jackrabbit.core.persistence.PMContext;
 import org.apache.jackrabbit.core.persistence.PersistenceManager;
+import org.apache.jackrabbit.core.persistence.check.ConsistencyCheckListener;
+import org.apache.jackrabbit.core.persistence.check.ConsistencyChecker;
+import org.apache.jackrabbit.core.persistence.check.ConsistencyReport;
 import org.apache.jackrabbit.core.persistence.util.BLOBStore;
 import org.apache.jackrabbit.core.persistence.util.FileBasedIndex;
+import org.apache.jackrabbit.core.persistence.util.NodeInfo;
 import org.apache.jackrabbit.core.persistence.util.NodePropBundle;
 import org.apache.jackrabbit.core.persistence.util.NodePropBundle.PropertyEntry;
 import org.apache.jackrabbit.core.state.ChangeLog;
@@ -51,7 +59,7 @@ import org.apache.jackrabbit.core.state.NoSuchItemStateException;
 import org.apache.jackrabbit.core.state.NodeReferences;
 import org.apache.jackrabbit.core.state.NodeState;
 import org.apache.jackrabbit.core.state.PropertyState;
-import org.apache.jackrabbit.core.stats.RepositoryStatisticsImpl;
+import org.apache.jackrabbit.stats.RepositoryStatisticsImpl;
 import org.apache.jackrabbit.core.util.StringIndex;
 import org.apache.jackrabbit.core.value.InternalValue;
 import org.apache.jackrabbit.spi.Name;
@@ -61,33 +69,36 @@ import org.slf4j.LoggerFactory;
 /**
  * The <code>AbstractBundlePersistenceManager</code> acts as base for all
  * persistence managers that store the state in a {@link NodePropBundle}.
- * <p/>
+ * <p>
  * The state and all property states of one node are stored together in one
- * record. Property values of a certain size can be store outside of the bundle.
+ * record. Property values of a certain size can be stored outside of the bundle.
  * This currently only works for binary properties. NodeReferences are not
  * included in the bundle since they are addressed by the target id.
- * <p/>
+ * <p>
  * Some strings like namespaces and local names are additionally managed by
  * separate indexes. only the index number is serialized to the records which
  * reduces the amount of memory used.
- * <p/>
+ * <p>
  * Special treatment is performed for the properties "jcr:uuid", "jcr:primaryType"
  * and "jcr:mixinTypes". As they are also stored in the node state they are not
  * included in the bundle but generated when required.
- * <p/>
- * In order to increase performance, there are 2 caches maintained. One is the
+ * <p>
+ * In order to increase performance, there are two caches being maintained. One is the
  * bundle cache that caches already loaded bundles. The other is the
  * {@link LRUNodeIdCache} that caches non-existent bundles. This is useful
  * because a lot of {@link #exists(NodeId)} calls are issued that would result
- * in a useless SQL execution if the desired bundle does not exist.
- * <p/>
+ * in a useless persistence lookup if the desired bundle does not exist.
+ * <p>
  * Configuration:<br>
  * <ul>
  * <li><param name="{@link #setBundleCacheSize(String) bundleCacheSize}" value="8"/>
  * </ul>
  */
 public abstract class AbstractBundlePersistenceManager implements
-    PersistenceManager, CachingPersistenceManager, IterablePersistenceManager, CacheAccessListener {
+    PersistenceManager, CachingPersistenceManager, IterablePersistenceManager, CacheAccessListener, ConsistencyChecker {
+
+    /** the audit logger */
+    private static Logger auditLogger = LoggerFactory.getLogger("org.apache.jackrabbit.core.audit");
 
     /** the default logger */
     private static Logger log = LoggerFactory.getLogger(AbstractBundlePersistenceManager.class);
@@ -108,6 +119,14 @@ public abstract class AbstractBundlePersistenceManager implements
     private static final NodePropBundle MISSING =
         new NodePropBundle(NodeId.randomId());
 
+    /**
+     * The size estimate for the MISSING NodePropBundle. The sum of:
+     * - ConcurrentCache.E: 32 bytes
+     * - LinkedHashMap.Entry: 64 bytes
+     * - NodeId: 32 bytes
+     */
+    private static final long MISSING_SIZE_ESTIMATE = 128;
+
     /** the index for namespaces */
     private StringIndex nsIndex;
 
@@ -153,10 +172,12 @@ public abstract class AbstractBundlePersistenceManager implements
     /** Duration of bundle read operations. */
     private AtomicLong cacheMissDuration;
 
-    
     /** Counter of bundle cache size. */
     private AtomicLong cacheSizeCounter;
 
+    /** The update event channel to use by the consistency checker when fixing inconsistencies */
+    private UpdateEventChannel eventChannel;
+
     /**
      * Returns the size of the bundle cache in megabytes.
      * @return the size of the bundle cache in megabytes.
@@ -349,6 +370,18 @@ public abstract class AbstractBundlePersistenceManager implements
         }
     }
 
+    //------------------------------------------< IterablePersistenceManager >--
+
+    @Override
+    public Map<NodeId, NodeInfo> getAllNodeInfos(NodeId after, int maxCount)
+            throws ItemStateException, RepositoryException {
+        Map<NodeId, NodeInfo> infos = new LinkedHashMap<NodeId, NodeInfo>();
+        for (NodeId nodeId : getAllNodeIds(after, maxCount)) {
+            infos.put(nodeId, new NodeInfo(loadBundle(nodeId)));
+        }
+        return infos;
+    }
+
     //----------------------------------------------------------------< spi >---
 
     /**
@@ -679,9 +712,12 @@ public abstract class AbstractBundlePersistenceManager implements
         }
 
         // now store all modified bundles
+        long updateSize = 0;
         for (NodePropBundle bundle : modified.values()) {
             putBundle(bundle);
+            updateSize += bundle.getSize();
         }
+        changeLog.setUpdateSize(updateSize);
 
         // store the refs
         for (NodeReferences refs : changeLog.modifiedRefs()) {
@@ -737,7 +773,7 @@ public abstract class AbstractBundlePersistenceManager implements
             bundle.markOld();
             bundles.put(id, bundle, bundle.getSize());
         } else {
-            bundles.put(id, MISSING, 16);
+            bundles.put(id, MISSING, MISSING_SIZE_ESTIMATE);
         }
         return bundle;
     }
@@ -751,7 +787,7 @@ public abstract class AbstractBundlePersistenceManager implements
     private void deleteBundle(NodePropBundle bundle) throws ItemStateException {
         destroyBundle(bundle);
         bundle.removeAllProperties(getBlobStore());
-        bundles.put(bundle.getId(), MISSING, 16);
+        bundles.put(bundle.getId(), MISSING, MISSING_SIZE_ESTIMATE);
     }
 
     /**
@@ -764,6 +800,9 @@ public abstract class AbstractBundlePersistenceManager implements
         long time = System.nanoTime();
         log.debug("Storing bundle {}", bundle.getId());
         storeBundle(bundle);
+        if (auditLogger.isDebugEnabled()) {
+        	auditLogger.debug("{} ({})", bundle.getId(), bundle.getSize());
+        }
         writeDuration.addAndGet(System.nanoTime() - time);
         writeCounter.incrementAndGet();
 
@@ -777,11 +816,38 @@ public abstract class AbstractBundlePersistenceManager implements
     }
 
     /**
-     * This implementation does nothing.
-     *
      * {@inheritDoc}
      */
     public void checkConsistency(String[] uuids, boolean recursive, boolean fix) {
+        try {
+            ConsistencyCheckerImpl checker = new ConsistencyCheckerImpl(this, null, null, eventChannel);
+            checker.check(uuids, recursive);
+            checker.doubleCheckErrors();
+            if (fix) {
+                checker.repair();
+            }
+        } catch (RepositoryException ex) {
+            log.error("While running consistency check.", ex);
+        }
+    }
+
+    public void setEventChannel(UpdateEventChannel eventChannel) {
+        this.eventChannel = eventChannel;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ConsistencyReport check(String[] uuids, boolean recursive,
+            boolean fix, String lostNFoundId, ConsistencyCheckListener listener)
+            throws RepositoryException {
+        ConsistencyCheckerImpl checker = new ConsistencyCheckerImpl(this, listener, lostNFoundId, eventChannel);
+        checker.check(uuids, recursive);
+        checker.doubleCheckErrors();
+        if (fix) {
+            checker.repair();
+        }
+        return checker.getReport();
     }
 
     /**
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/BundleFsPersistenceManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/BundleFsPersistenceManager.java
index e4f3026..200d6d8 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/BundleFsPersistenceManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/BundleFsPersistenceManager.java
@@ -42,11 +42,12 @@ import java.io.OutputStream;
 import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 /**
  * This is a generic persistence manager that stores the {@link NodePropBundle}s
  * in a filesystem.
- * <p/>
+ * <p>
  * Configuration:<br>
  * <ul>
  * <li><param name="{@link #setBlobFSBlockSize(String) blobFSBlockSize}" value="0"/>
@@ -482,7 +483,7 @@ public class BundleFsPersistenceManager extends AbstractBundlePersistenceManager
     /**
      * {@inheritDoc}
      */
-    public Iterable<NodeId> getAllNodeIds(NodeId bigger, int maxCount)
+    public List<NodeId> getAllNodeIds(NodeId bigger, int maxCount)
             throws ItemStateException {
         ArrayList<NodeId> list = new ArrayList<NodeId>();
         try {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/ConsistencyCheckerError.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/ConsistencyCheckerError.java
new file mode 100644
index 0000000..ccc2ac7
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/ConsistencyCheckerError.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.persistence.bundle;
+
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.persistence.check.ReportItem;
+import org.apache.jackrabbit.core.persistence.check.ReportItemImpl;
+import org.apache.jackrabbit.core.state.ChangeLog;
+import org.apache.jackrabbit.core.state.ItemStateException;
+
+/**
+ * Base class for errors reported by the {@link ConsistencyCheckerImpl}
+ */
+abstract class ConsistencyCheckerError {
+
+    protected final String message;
+    protected final NodeId nodeId;
+    protected boolean repaired;
+
+    ConsistencyCheckerError(NodeId nodeId, String message) {
+        this.nodeId = nodeId;
+        this.message = message;
+    }
+
+    final NodeId getNodeId() {
+        return nodeId;
+    }
+
+    final void repair(final ChangeLog changes) throws ItemStateException {
+        doRepair(changes);
+        repaired = true;
+    }
+
+    final ReportItem getReportItem() {
+        return new ReportItemImpl(nodeId.toString(), message, getType(), repaired);
+    }
+
+    /**
+     * @return whether this error is repairable
+     */
+    abstract boolean isRepairable();
+
+    /**
+     * Repair this error and update the changelog.
+     *
+     * @param changes  the changelog to update with the changes made.
+     * @throws ItemStateException
+     */
+    abstract void doRepair(final ChangeLog changes) throws ItemStateException;
+
+    abstract ReportItem.Type getType();
+
+    /**
+     * Double check the error to eliminate false positives in live environments.
+     * @return  whether the error was confirmed.
+     * @throws ItemStateException
+     */
+    abstract boolean doubleCheck() throws ItemStateException;
+
+    @Override
+    public String toString() {
+        return getType() + " - " + getNodeId();
+    }
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/ConsistencyCheckerImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/ConsistencyCheckerImpl.java
new file mode 100644
index 0000000..32e1d03
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/ConsistencyCheckerImpl.java
@@ -0,0 +1,707 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.persistence.bundle;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.core.cluster.ClusterException;
+import org.apache.jackrabbit.core.cluster.Update;
+import org.apache.jackrabbit.core.cluster.UpdateEventChannel;
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.observation.EventState;
+import org.apache.jackrabbit.core.persistence.check.ConsistencyCheckListener;
+import org.apache.jackrabbit.core.persistence.check.ConsistencyReport;
+import org.apache.jackrabbit.core.persistence.check.ConsistencyReportImpl;
+import org.apache.jackrabbit.core.persistence.check.ReportItem;
+import org.apache.jackrabbit.core.persistence.util.NodeInfo;
+import org.apache.jackrabbit.core.persistence.util.NodePropBundle;
+import org.apache.jackrabbit.core.state.ChangeLog;
+import org.apache.jackrabbit.core.state.DummyUpdateEventChannel;
+import org.apache.jackrabbit.core.state.ItemState;
+import org.apache.jackrabbit.core.state.ItemStateException;
+import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.NameFactory;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ConsistencyCheckerImpl {
+
+    private static Logger log = LoggerFactory.getLogger(ConsistencyCheckerImpl.class);
+
+    /**
+     * The number of nodes to fetch at once from the persistence manager. Defaults to 8kb
+     */
+    private static final int NODESATONCE = Integer.getInteger("org.apache.jackrabbit.checker.nodesatonce", 1024 * 8);
+
+    /**
+     * Attribute name used to store the size of the update.
+     */
+    private static final String ATTRIBUTE_UPDATE_SIZE = "updateSize";
+
+    private final AbstractBundlePersistenceManager pm;
+    private final ConsistencyCheckListener listener;
+    private NodeId lostNFoundId;
+    private UpdateEventChannel eventChannel = new DummyUpdateEventChannel();
+    private Map<NodeId, NodePropBundle> bundles;
+    private List<ConsistencyCheckerError> errors;
+    private int nodeCount;
+    private long elapsedTime;
+
+    public ConsistencyCheckerImpl(AbstractBundlePersistenceManager pm, ConsistencyCheckListener listener,
+                                  String lostNFoundId, final UpdateEventChannel eventChannel) {
+        this.pm = pm;
+        this.listener = listener;
+        if (lostNFoundId != null) {
+            this.lostNFoundId = new NodeId(lostNFoundId);
+        }
+        if (eventChannel != null) {
+            this.eventChannel = eventChannel;
+        }
+    }
+
+    /**
+     * Check the database for inconsistencies.
+     *
+     * @param uuids a list of node identifiers to check or {@code null} in order to check all nodes
+     * @param recursive  whether to recursively check the subtrees below the nodes identified by the provided uuids
+     * @throws RepositoryException
+     */
+    public void check(String[] uuids, boolean recursive) throws RepositoryException {
+        errors = new ArrayList<ConsistencyCheckerError>();
+        long tstart = System.currentTimeMillis();
+        nodeCount = internalCheckConsistency(uuids, recursive);
+        elapsedTime = System.currentTimeMillis() - tstart;
+    }
+
+    /**
+     * Do a double check on the errors found during {@link #check}.
+     * Removes all false positives from the report.
+     */
+    public void doubleCheckErrors() {
+        if (hasErrors()) {
+            final Iterator<ConsistencyCheckerError> errorIterator = errors.iterator();
+            while (errorIterator.hasNext()) {
+                final ConsistencyCheckerError error = errorIterator.next();
+                try {
+                    if (!error.doubleCheck()) {
+                        info(null, "False positive: " + error);
+                        errorIterator.remove();
+                    }
+                } catch (ItemStateException e) {
+                    error(null, "Failed to double check error: " + error, e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Return the report of a consistency {@link #check} / {@link #doubleCheckErrors()} / {@link #repair}
+     */
+    public ConsistencyReport getReport() {
+        final Set<ReportItem> reportItems = new HashSet<ReportItem>();
+        if (hasErrors()) {
+            for (ConsistencyCheckerError error : errors) {
+                reportItems.add(error.getReportItem());
+            }
+        }
+        return new ConsistencyReportImpl(nodeCount, elapsedTime, reportItems);
+    }
+
+    /**
+     * Repair any errors found during a {@link #check}. Should be run after a {#check} and
+     * (if needed) {@link #doubleCheckErrors}.
+     *
+     * @throws RepositoryException
+     */
+    public void repair() throws RepositoryException {
+        checkLostNFound();
+        bundles = new HashMap<NodeId, NodePropBundle>();
+        if (hasRepairableErrors()) {
+            boolean successful = false;
+            final CheckerUpdate update = new CheckerUpdate();
+            try {
+                eventChannel.updateCreated(update);
+                for (ConsistencyCheckerError error : errors) {
+                    if (error.isRepairable()) {
+                        try {
+                            error.repair(update.getChanges());
+                            info(null, "Repairing " + error);
+                        } catch (ItemStateException e) {
+                            error(null, "Failed to repair error: " + error, e);
+                        }
+                    }
+                }
+
+                final ChangeLog changes = update.getChanges();
+                if (changes.hasUpdates()) {
+                    eventChannel.updatePrepared(update);
+                    for (NodePropBundle bundle : bundles.values()) {
+                        storeBundle(bundle);
+                    }
+                    update.setAttribute(ATTRIBUTE_UPDATE_SIZE, changes.getUpdateSize());
+                    successful = true;
+                }
+            } catch (ClusterException e) {
+                throw new RepositoryException("Cannot create update", e);
+            } finally {
+                if (successful) {
+                    eventChannel.updateCommitted(update, "checker@");
+                } else {
+                    eventChannel.updateCancelled(update);
+                }
+            }
+        }
+    }
+
+    private boolean hasErrors() {
+        return errors != null && !errors.isEmpty();
+    }
+
+    private boolean hasRepairableErrors() {
+        if (hasErrors()) {
+            for (ConsistencyCheckerError error : errors) {
+                if (error.isRepairable()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private void checkLostNFound() {
+        if (lostNFoundId != null) {
+            // do we have a "lost+found" node?
+            try {
+                NodePropBundle lfBundle = pm.loadBundle(lostNFoundId);
+                if (lfBundle == null) {
+                    error(lostNFoundId.toString(), "Specified 'lost+found' node does not exist");
+                    lostNFoundId = null;
+                } else if (!NameConstants.NT_UNSTRUCTURED.equals(lfBundle .getNodeTypeName())) {
+                    error(lostNFoundId.toString(), "Specified 'lost+found' node is not of type nt:unstructured");
+                    lostNFoundId = null;
+                }
+            } catch (Exception ex) {
+                error(lostNFoundId.toString(), "finding 'lost+found' folder", ex);
+                lostNFoundId = null;
+            }
+        } else {
+            info(null, "No 'lost+found' node specified: orphans cannot be fixed");
+        }
+    }
+
+    private int internalCheckConsistency(String[] uuids, boolean recursive) throws RepositoryException {
+        int count = 0;
+
+        if (uuids == null) {
+            // check all nodes
+            try {
+                Map<NodeId, NodeInfo> batch = pm.getAllNodeInfos(null, NODESATONCE);
+                Map<NodeId, NodeInfo> allInfos = batch;
+
+                NodeId lastId = null;
+                while (!batch.isEmpty()) {
+
+                    for (Map.Entry<NodeId, NodeInfo> entry : batch.entrySet()) {
+                        lastId = entry.getKey();
+
+                        count++;
+                        if (count % 1000 == 0) {
+                            log.info(pm + ": loaded " + count + " infos...");
+                        }
+
+                    }
+
+                    batch = pm.getAllNodeInfos(lastId, NODESATONCE);
+
+                    allInfos.putAll(batch);
+                }
+
+                if (pm.exists(lastId)) {
+                    for (Map.Entry<NodeId, NodeInfo> entry : allInfos.entrySet()) {
+                        checkBundleConsistency(entry.getKey(), entry.getValue(), allInfos);
+                    }
+                } else {
+                    log.info("Failed to read all nodes, starting over");
+                    internalCheckConsistency(uuids, recursive);
+                }
+
+            } catch (ItemStateException e) {
+                throw new RepositoryException("Error loading nodes", e);
+            } finally {
+                NodeInfo.clearPool();
+            }
+        } else {
+            // check only given uuids, handle recursive flag
+
+            List<NodeId> idList = new ArrayList<NodeId>(uuids.length);
+            for (final String uuid : uuids) {
+                try {
+                    idList.add(new NodeId(uuid));
+                } catch (IllegalArgumentException e) {
+                    error(uuid, "Invalid id for consistency check, skipping: '" + uuid + "': " + e);
+                }
+            }
+
+            for (int i = 0; i < idList.size(); i++) {
+                NodeId id = idList.get(i);
+                try {
+                    final NodePropBundle bundle = pm.loadBundle(id);
+                    if (bundle == null) {
+                        if (!isVirtualNode(id)) {
+                            error(id.toString(), "No bundle found for id '" + id + "'");
+                        }
+                    } else {
+                        checkBundleConsistency(id, new NodeInfo(bundle), Collections.<NodeId, NodeInfo>emptyMap());
+
+                        if (recursive) {
+                            for (NodePropBundle.ChildNodeEntry entry : bundle.getChildNodeEntries()) {
+                                idList.add(entry.getId());
+                            }
+                        }
+
+                        count++;
+                        if (count % 1000 == 0 && listener == null) {
+                            log.info(pm + ": checked " + count + "/" + idList.size() + " bundles...");
+                        }
+                    }
+                } catch (ItemStateException ignored) {
+                    // problem already logged
+                }
+            }
+        }
+
+        log.info(pm + ": checked " + count + " bundles.");
+
+        return count;
+    }
+
+    /**
+     * Checks a single bundle for inconsistencies, ie. inexistent child nodes, inexistent parents, and other
+     * structural inconsistencies.
+     *
+     * @param nodeId node id for the bundle to check
+     * @param nodeInfo the node info for the node to check
+     * @param infos all the {@link NodeInfo}s loaded in the current batch
+     */
+    private void checkBundleConsistency(NodeId nodeId, NodeInfo nodeInfo, Map<NodeId, NodeInfo> infos) {
+
+        // skip all virtual nodes
+        if (!isRoot(nodeId) && isVirtualNode(nodeId)) {
+            return;
+        }
+
+        if (listener != null) {
+            listener.startCheck(nodeId.toString());
+        }
+
+        // check the children
+        for (final NodeId childNodeId : nodeInfo.getChildren()) {
+
+            if (isVirtualNode(childNodeId)) {
+                continue;
+            }
+
+            NodeInfo childNodeInfo = infos.get(childNodeId);
+
+            if (childNodeInfo == null) {
+                addError(new MissingChild(nodeId, childNodeId));
+            } else {
+                if (!nodeId.equals(childNodeInfo.getParentId())) {
+                    addError(new DisconnectedChild(nodeId, childNodeId, childNodeInfo.getParentId()));
+                }
+            }
+        }
+
+        // check the parent
+        NodeId parentId = nodeInfo.getParentId();
+        // skip root nodes
+        if (parentId != null && !isRoot(nodeId)) {
+            NodeInfo parentInfo = infos.get(parentId);
+
+            if (parentInfo == null) {
+                addError(new OrphanedNode(nodeId, parentId));
+            } else {
+                // if the parent exists, does it have a child node entry for us?
+                boolean found = false;
+
+                for (NodeId childNodeId : parentInfo.getChildren()) {
+                    if (childNodeId.equals(nodeId)){
+                        found = true;
+                        break;
+                    }
+                }
+
+                if (!found) {
+                    addError(new AbandonedNode(nodeId, parentId));
+                }
+
+            }
+        }
+    }
+
+    protected boolean isVirtualNode(NodeId nodeId) {
+        return nodeId.toString().endsWith("babecafebabe");
+    }
+
+    private boolean isRoot(NodeId nodeId) {
+        return "cafebabe-cafe-babe-cafe-babecafebabe".equals(nodeId.toString());
+    }
+
+    private void addError(ConsistencyCheckerError error) {
+        if (listener != null) {
+            listener.report(error.getReportItem());
+        }
+        errors.add(error);
+    }
+
+    private void info(String id, String message) {
+        if (this.listener == null) {
+            String idstring = id == null ? "" : ("Node " + id + ": ");
+            log.info(idstring + message);
+        } else {
+            listener.info(id, message);
+        }
+    }
+
+    private void error(String id, String message) {
+        if (this.listener == null) {
+            String idstring = id == null ? "" : ("Node " + id + ": ");
+            log.error(idstring + message);
+        } else {
+            listener.error(id, message);
+        }
+    }
+
+    private void error(String id, String message, Throwable ex) {
+        String idstring = id == null ? "" : ("Node " + id + ": ");
+        log.error(idstring + message, ex);
+        if (listener != null) {
+            listener.error(id, message);
+        }
+    }
+
+    private void storeBundle(NodePropBundle bundle) {
+        try {
+            bundle.markOld();
+            bundle.setModCount((short) (bundle.getModCount()+1));
+            pm.storeBundle(bundle);
+            pm.evictBundle(bundle.getId());
+        } catch (ItemStateException e) {
+            log.error(pm + ": Error storing fixed bundle: " + e);
+        }
+    }
+
+    private NodePropBundle getBundle(NodeId nodeId) throws ItemStateException {
+        if (bundles.containsKey(nodeId)) {
+            return bundles.get(nodeId);
+        }
+        return pm.loadBundle(nodeId);
+    }
+
+    private void saveBundle(NodePropBundle bundle) {
+        bundles.put(bundle.getId(), bundle);
+    }
+
+    /**
+     * A missing child is when the node referred to by a child node entry
+     * does not exist.
+     *
+     * This type of error is repaired by removing the corrupted child node entry.
+     */
+    private class MissingChild extends ConsistencyCheckerError {
+
+        private final NodeId childNodeId;
+
+        private MissingChild(final NodeId nodeId, final NodeId childNodeId) {
+            super(nodeId, "NodeState '" + nodeId + "' references inexistent child '" + childNodeId + "'");
+            this.childNodeId = childNodeId;
+        }
+
+        @Override
+        ReportItem.Type getType() {
+            return ReportItem.Type.MISSING;
+        }
+
+        @Override
+        boolean isRepairable() {
+            return true;
+        }
+
+        @Override
+        void doRepair(final ChangeLog changes) throws ItemStateException {
+            final NodePropBundle bundle = getBundle(nodeId);
+            final Iterator<NodePropBundle.ChildNodeEntry> entryIterator = bundle.getChildNodeEntries().iterator();
+            while (entryIterator.hasNext()) {
+                final NodePropBundle.ChildNodeEntry childNodeEntry = entryIterator.next();
+                if (childNodeEntry.getId().equals(childNodeId)) {
+                    entryIterator.remove();
+                    saveBundle(bundle);
+                    changes.modified(new NodeState(nodeId, null, null, ItemState.STATUS_EXISTING, false));
+                }
+            }
+        }
+
+        @Override
+        boolean doubleCheck() throws ItemStateException {
+            final NodePropBundle childBundle = pm.loadBundle(childNodeId);
+            if (childBundle == null) {
+                final NodePropBundle bundle = pm.loadBundle(nodeId);
+                if (bundle != null) {
+                    for (NodePropBundle.ChildNodeEntry entry : bundle.getChildNodeEntries()) {
+                        if (entry.getId().equals(childNodeId)) {
+                            return true;
+                        }
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * A disconnected child is when a child node entry refers to a node
+     * that exists, but that node actually has a different parent.
+     *
+     * This type of error is repaired by removing the corrupted child node entry.
+     */
+    private class DisconnectedChild extends ConsistencyCheckerError {
+
+        private final NodeId childNodeId;
+
+        DisconnectedChild(final NodeId nodeId, final NodeId childNodeId, final NodeId invalidParentId) {
+            super(nodeId, "Node has invalid parent id: '" + invalidParentId + "' (instead of '" + nodeId + "')");
+            this.childNodeId = childNodeId;
+        }
+
+        @Override
+        ReportItem.Type getType() {
+            return ReportItem.Type.DISCONNECTED;
+        }
+
+        @Override
+        boolean isRepairable() {
+            return true;
+        }
+
+        @Override
+        void doRepair(final ChangeLog changes) throws ItemStateException {
+            NodePropBundle bundle = getBundle(nodeId);
+            final Iterator<NodePropBundle.ChildNodeEntry> entryIterator = bundle.getChildNodeEntries().iterator();
+            while (entryIterator.hasNext()) {
+                final NodePropBundle.ChildNodeEntry childNodeEntry = entryIterator.next();
+                if (childNodeEntry.getId().equals(childNodeId)) {
+                    entryIterator.remove();
+                    saveBundle(bundle);
+                    changes.modified(new NodeState(nodeId, null, null, ItemState.STATUS_EXISTING, false));
+                    break;
+                }
+            }
+        }
+
+        @Override
+        boolean doubleCheck() throws ItemStateException {
+            final NodePropBundle childBundle = pm.loadBundle(childNodeId);
+            if (childBundle != null && !childBundle.getParentId().equals(nodeId)) {
+                final NodePropBundle bundle = pm.loadBundle(nodeId);
+                if (bundle != null) {
+                    // double check if the child node entry is still there
+                    for (NodePropBundle.ChildNodeEntry entry : bundle.getChildNodeEntries()) {
+                        if (entry.getId().equals(childNodeId)) {
+                            return true;
+                        }
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * An orphaned node is a node whose parent does not exist.
+     *
+     * This type of error is repaired by reattaching the orphan to
+     * a special purpose 'lost and found' node.
+     */
+    private class OrphanedNode extends ConsistencyCheckerError {
+
+        private final NodeId parentNodeId;
+
+        OrphanedNode(final NodeId nodeId, final NodeId parentNodeId) {
+            super(nodeId, "NodeState '" + nodeId + "' references inexistent parent id '" + parentNodeId + "'");
+            this.parentNodeId = parentNodeId;
+        }
+
+        @Override
+        ReportItem.Type getType() {
+            return ReportItem.Type.ORPHANED;
+        }
+
+        @Override
+        boolean isRepairable() {
+            return lostNFoundId != null;
+        }
+
+        @Override
+        void doRepair(final ChangeLog changes) throws ItemStateException {
+            if (lostNFoundId != null) {
+                final NodePropBundle bundle = getBundle(nodeId);
+                final NodePropBundle lfBundle = getBundle(lostNFoundId);
+
+                final String nodeName = nodeId + "-" + System.currentTimeMillis();
+                final NameFactory nameFactory = NameFactoryImpl.getInstance();
+                lfBundle.addChildNodeEntry(nameFactory.create("", nodeName), nodeId);
+                bundle.setParentId(lostNFoundId);
+
+                saveBundle(bundle);
+                saveBundle(lfBundle);
+
+                changes.modified(new NodeState(lostNFoundId, null, null, ItemState.STATUS_EXISTING, false));
+                changes.modified(new NodeState(nodeId, null, null, ItemState.STATUS_EXISTING, false));
+            }
+        }
+
+        @Override
+        boolean doubleCheck() throws ItemStateException {
+            final NodePropBundle parentBundle = pm.loadBundle(parentNodeId);
+            if (parentBundle == null) {
+                final NodePropBundle bundle = pm.loadBundle(nodeId);
+                if (bundle != null) {
+                    if (parentNodeId.equals(bundle.getParentId())) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * An abandoned node is a node that points to an existing node
+     * as its parent, but that parent node does not have a corresponding
+     * child node entry for the child.
+     *
+     * This type of error is repaired by adding the missing child node entry
+     * to the parent.
+     */
+    private class AbandonedNode extends ConsistencyCheckerError {
+
+        private final NodeId nodeId;
+        private final NodeId parentNodeId;
+
+        AbandonedNode(final NodeId nodeId, final NodeId parentNodeId) {
+            super(nodeId, "NodeState '" + nodeId + "' is not referenced by its parent node '" + parentNodeId + "'");
+            this.nodeId = nodeId;
+            this.parentNodeId = parentNodeId;
+        }
+
+        @Override
+        ReportItem.Type getType() {
+            return ReportItem.Type.ABANDONED;
+        }
+
+        @Override
+        boolean isRepairable() {
+            return true;
+        }
+
+        @Override
+        void doRepair(final ChangeLog changes) throws ItemStateException {
+            final NodePropBundle parentBundle = getBundle(parentNodeId);
+
+            parentBundle.addChildNodeEntry(createNodeName(), nodeId);
+
+            saveBundle(parentBundle);
+            changes.modified(new NodeState(parentNodeId, null, null, ItemState.STATUS_EXISTING, false));
+        }
+
+        private Name createNodeName() {
+            int n = (int) System.currentTimeMillis() + new Random().nextInt();
+            final String localName = Integer.toHexString(n);
+            final NameFactory nameFactory = NameFactoryImpl.getInstance();
+            return nameFactory.create("{}" + localName);
+        }
+
+        @Override
+        boolean doubleCheck() throws ItemStateException {
+            final NodePropBundle parentBundle = pm.loadBundle(parentNodeId);
+            if (parentBundle != null) {
+                for (NodePropBundle.ChildNodeEntry entry : parentBundle.getChildNodeEntries()) {
+                    if (entry.getId().equals(nodeId)) {
+                        return false;
+                    }
+                }
+            }
+            final NodePropBundle bundle = pm.loadBundle(nodeId);
+            if (bundle != null) {
+                if (parentNodeId.equals(bundle.getParentId())) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    private class CheckerUpdate implements Update {
+
+        private final Map<String, Object> attributes = new HashMap<String, Object>();
+        private final ChangeLog changeLog = new ChangeLog();
+        private final long timestamp = System.currentTimeMillis();
+
+        @Override
+        public void setAttribute(final String name, final Object value) {
+            attributes.put(name, value);
+        }
+
+        @Override
+        public Object getAttribute(final String name) {
+            return attributes.get(name);
+        }
+
+        @Override
+        public ChangeLog getChanges() {
+            return changeLog;
+        }
+
+        @Override
+        public List<EventState> getEvents() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public long getTimestamp() {
+            return timestamp;
+        }
+
+        @Override
+        public String getUserData() {
+            return null;
+        }
+    }
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ConsistencyCheckListener.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ConsistencyCheckListener.java
new file mode 100644
index 0000000..c0fc074
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ConsistencyCheckListener.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.persistence.check;
+
+public interface ConsistencyCheckListener {
+
+    /**
+     * Called when checking of a node starts
+     * @param id node ID
+     */
+    public void startCheck(String id);
+
+    /**
+     * Called when there's a consistency problem to be reported
+     * @param item problem report
+     */
+    public void report(ReportItem item);
+
+    /**
+     * Called on errors with the check procedure
+     * @param id node id (can be <code>null</code>)
+     * @param message
+     */
+    public void error(String id, String message);
+
+    /**
+     * Called on progress with the check procedure
+     * @param id node id (can be <code>null</code>)
+     * @param message
+     */
+    public void info(String id, String message);
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ConsistencyChecker.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ConsistencyChecker.java
index ac80ebd..e771a14 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ConsistencyChecker.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ConsistencyChecker.java
@@ -18,17 +18,26 @@ package org.apache.jackrabbit.core.persistence.check;
 
 import javax.jcr.RepositoryException;
 
+import org.apache.jackrabbit.core.cluster.UpdateEventChannel;
+
 /**
  * Optional interface for Persistence Managers. Allows running consistency
- * checks similar to the base one (see @link
- * {@link PersistenceManager#checkConsistency(String[], boolean, boolean)} but
- * providing a result that can be acted upon.
+ * checks similar to the base one (see
+ * {@link org.apache.jackrabbit.core.persistence.bundle.AbstractBundlePersistenceManager#checkConsistency(String[], boolean, boolean)})
+ * but providing a result that can be acted upon.
  * <p>
  * <em>Beware: this interface is designed for unit tests only.</em>
  */
 public interface ConsistencyChecker {
 
     /**
+     * Set the update event channel. Needed to inform the cluster of any changes made during repairs.
+     *
+     * @param eventChannel the update event channel
+     */
+    public void setEventChannel(UpdateEventChannel eventChannel);
+
+    /**
      * Perform a consistency check of the data. An example are non-existent
      * nodes referenced in a child node entry. The existence of this feature and
      * the scope of the implementation can vary in different PersistenceManager
@@ -44,6 +53,15 @@ public interface ConsistencyChecker {
      *            if true, any problems found that can be repaired will be
      *            repaired. if false, no data will be modified, instead all
      *            inconsistencies will only get logged
+     * @param lostNFoundId
+     *            node to which to attach orphaned nodes (or <code>null</code>,
+     *            in which case orphaned nodes will not get moved); this node
+     *            should be of a node type that allows adding arbitrary child
+     *            nodes
+     * @param listener
+     *            to be called on each node that was checked (may be <code>null</code>)
      */
-    ConsistencyReport check(String[] uuids, boolean recursive, boolean fix) throws RepositoryException;
+    ConsistencyReport check(String[] uuids, boolean recursive, boolean fix,
+            String lostNFoundId, ConsistencyCheckListener listener)
+            throws RepositoryException;
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ReportItem.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ReportItem.java
index 988a767..ed51f6e 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ReportItem.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ReportItem.java
@@ -17,10 +17,14 @@
 package org.apache.jackrabbit.core.persistence.check;
 
 /**
- * An item reported inside a {@link ConsistencyChecker.Report}.
+ * An item reported inside a {@link ConsistencyReport}.
  */
 public interface ReportItem {
 
+    public enum Type {
+        ORPHANED, DISCONNECTED, ABANDONED, MISSING, ERROR
+    }
+
     /**
      * @return node id to which the message applies
      */
@@ -30,4 +34,14 @@ public interface ReportItem {
      * @return message
      */
     public String getMessage();
+
+    /**
+     * @return the type of inconsistency
+     */
+    public Type getType();
+
+    /**
+     * @return whether this error was repaired
+     */
+    public boolean isRepaired();
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ReportItemImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ReportItemImpl.java
index 132c03c..2b40e68 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ReportItemImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ReportItemImpl.java
@@ -20,10 +20,14 @@ public class ReportItemImpl implements ReportItem {
 
     private final String nodeId;
     private final String message;
+    private final Type type;
+    private final boolean repaired;
 
-    public ReportItemImpl(String nodeId, String message) {
+    public ReportItemImpl(String nodeId, String message, Type type, boolean repaired) {
         this.nodeId = nodeId;
         this.message = message;
+        this.type = type;
+        this.repaired = repaired;
     }
 
     public String getNodeId() {
@@ -35,7 +39,17 @@ public class ReportItemImpl implements ReportItem {
     }
 
     @Override
+    public Type getType() {
+        return type;
+    }
+
+    @Override
+    public boolean isRepaired() {
+        return repaired;
+    }
+
+    @Override
     public String toString() {
-        return nodeId + " -- " + message;
+        return type + ": " + nodeId + " -- " + message;
     }
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/DatabasePersistenceManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/DatabasePersistenceManager.java
index 2ad08b5..fd5b39e 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/DatabasePersistenceManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/DatabasePersistenceManager.java
@@ -447,7 +447,7 @@ public abstract class DatabasePersistenceManager extends AbstractPersistenceMana
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * This method uses shared <code>PreparedStatement</code>s which must
      * be executed strictly sequentially. Because this method synchronizes on
      * the persistence manager instance there is no need to synchronize on the
@@ -486,7 +486,7 @@ public abstract class DatabasePersistenceManager extends AbstractPersistenceMana
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * This method uses shared <code>PreparedStatement</code>s which must
      * be executed strictly sequentially. Because this method synchronizes on
      * the persistence manager instance there is no need to synchronize on the
@@ -625,7 +625,7 @@ public abstract class DatabasePersistenceManager extends AbstractPersistenceMana
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * This method uses shared <code>PreparedStatement</code>s which must
      * be executed strictly sequentially. Because this method synchronizes on
      * the persistence manager instance there is no need to synchronize on the
@@ -898,7 +898,7 @@ public abstract class DatabasePersistenceManager extends AbstractPersistenceMana
                     // no more trials, re-throw
                     throw se;
                 }
-                log.warn("execute failed, about to reconnect...", se.getMessage());
+                log.warn("execute failed, about to reconnect... {}", se.getMessage());
 
                 // try to reconnect
                 if (reestablishConnection()) {
@@ -930,7 +930,7 @@ public abstract class DatabasePersistenceManager extends AbstractPersistenceMana
     /**
      * Resets the given <code>PreparedStatement</code> by clearing the parameters
      * and warnings contained.
-     * <p/>
+     * <p>
      * NOTE: This method MUST be called in a synchronized context as neither
      * this method nor the <code>PreparedStatement</code> instance on which it
      * operates are thread safe.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/DerbyPersistenceManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/DerbyPersistenceManager.java
index a3559af..ad5849b 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/DerbyPersistenceManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/DerbyPersistenceManager.java
@@ -30,7 +30,7 @@ import java.sql.SQLException;
  * embedded or standalone Derby database using a simple custom serialization format and a
  * very basic non-normalized database schema (in essence tables with one 'key'
  * and one 'data' column).
- * <p/>
+ * <p>
  * It is configured through the following properties:
  * <ul>
  * <li><code>url</code>: the database url of the form
@@ -51,7 +51,7 @@ import java.sql.SQLException;
  * set this to <code>false</code> when using a standalone database</li>
  * </ul>
  * See also {@link SimpleDbPersistenceManager}.
- * <p/>
+ * <p>
  * The following is a fragment from a sample configuration:
  * <pre>
  *   <PersistenceManager class="org.apache.jackrabbit.core.persistence.db.DerbyPersistenceManager">
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/MSSqlPersistenceManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/MSSqlPersistenceManager.java
index 3131dee..9cad8e9 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/MSSqlPersistenceManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/MSSqlPersistenceManager.java
@@ -25,7 +25,7 @@ import org.apache.jackrabbit.util.Text;
  * database using a simple custom serialization format and a
  * very basic non-normalized database schema (in essence tables with one 'key'
  * and one 'data' column).
- * <p/>
+ * <p>
  * It is configured through the following properties:
  * <ul>
  * <li><code>driver</code>: the FQN name of the JDBC driver class
@@ -43,7 +43,7 @@ import org.apache.jackrabbit.util.Text;
  * if <code>false</code> BLOBs are stored in the database</li>
  * </ul>
  * See also {@link SimpleDbPersistenceManager}.
- * <p/>
+ * <p>
  * The following is a fragment from a sample configuration:
  * <pre>
  *   <PersistenceManager class="org.apache.jackrabbit.core.persistence.db.MSSqlPersistenceManager">
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/OraclePersistenceManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/OraclePersistenceManager.java
index 5579ea5..785d95e 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/OraclePersistenceManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/OraclePersistenceManager.java
@@ -24,6 +24,7 @@ import org.apache.jackrabbit.core.state.ItemStateException;
 import org.apache.jackrabbit.core.state.NodeState;
 import org.apache.jackrabbit.core.state.PropertyState;
 import org.apache.jackrabbit.core.state.ItemState;
+import org.apache.jackrabbit.core.util.db.ConnectionFactory;
 import org.apache.jackrabbit.util.Text;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -50,7 +51,7 @@ import java.sql.Statement;
  * database using a simple custom serialization format and a
  * very basic non-normalized database schema (in essence tables with one 'key'
  * and one 'data' column).
- * <p/>
+ * <p>
  * It is configured through the following properties:
  * <ul>
  * <li><code>driver</code>: the FQN name of the JDBC driver class
@@ -68,7 +69,7 @@ import java.sql.Statement;
  * if <code>false</code> BLOBs are stored in the database</li>
  * </ul>
  * See also {@link SimpleDbPersistenceManager}.
- * <p/>
+ * <p>
  * The following is a fragment from a sample configuration:
  * <pre>
  *   <PersistenceManager class="org.apache.jackrabbit.core.persistence.db.OraclePersistenceManager">
@@ -139,7 +140,7 @@ public class OraclePersistenceManager extends SimpleDbPersistenceManager {
     //---------------------------------< SimpleDbPersistenceManager overrides >
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Retrieve the <code>oracle.sql.BLOB</code> class via reflection, and
      * initialize the values for the <code>DURATION_SESSION</code> and
      * <code>MODE_READWRITE</code> constants defined there.
@@ -289,7 +290,7 @@ public class OraclePersistenceManager extends SimpleDbPersistenceManager {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Overridden in order to support multiple oracle schemas. Note that
      * schema names in Oracle correspond to the username of the connection.
      * See http://issues.apache.org/jira/browse/JCR-582
@@ -374,8 +375,9 @@ public class OraclePersistenceManager extends SimpleDbPersistenceManager {
         */
         Method createTemporary = blobClass.getMethod("createTemporary",
                 new Class[]{Connection.class, Boolean.TYPE, Integer.TYPE});
-        Object blob = createTemporary.invoke(null,
-                new Object[]{con, Boolean.FALSE, durationSessionConstant});
+        Object blob = createTemporary.invoke(null, new Object[]{
+                ConnectionFactory.unwrap(con),
+                Boolean.FALSE, durationSessionConstant });
         Method open = blobClass.getMethod("open", new Class[]{Integer.TYPE});
         open.invoke(blob, new Object[]{modeReadWriteConstant});
         Method getBinaryOutputStream =
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/SimpleDbPersistenceManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/SimpleDbPersistenceManager.java
index 4a4781c..7fa629b 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/SimpleDbPersistenceManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/db/SimpleDbPersistenceManager.java
@@ -31,7 +31,7 @@ import javax.jcr.RepositoryException;
  * simple custom binary serialization format (see {@link Serializer}) and a
  * very basic non-normalized database schema (in essence tables with one 'key'
  * and one 'data' column).
- * <p/>
+ * <p>
  * It is configured through the following properties:
  * <ul>
  * <li><code>driver</code>: the FQN name of the JDBC driver class</li>
@@ -52,7 +52,7 @@ import javax.jcr.RepositoryException;
  * <code>java.sql.Statement.execute(String)</code> where every occurrence of the
  * the string <code>"${schemaObjectPrefix}"</code> has been replaced with the
  * value of the property <code>schemaObjectPrefix</code>.
- * <p/>
+ * <p>
  * The following is a fragment from a sample configuration using MySQL:
  * <pre>
  *   <PersistenceManager class="org.apache.jackrabbit.core.persistence.db.SimpleDbPersistenceManager">
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/mem/InMemBundlePersistenceManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/mem/InMemBundlePersistenceManager.java
index 4bac51d..f8f2fbb 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/mem/InMemBundlePersistenceManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/mem/InMemBundlePersistenceManager.java
@@ -25,7 +25,10 @@ import java.io.DataOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 import javax.jcr.RepositoryException;
@@ -42,6 +45,7 @@ import org.apache.jackrabbit.core.persistence.util.BLOBStore;
 import org.apache.jackrabbit.core.persistence.util.BundleBinding;
 import org.apache.jackrabbit.core.persistence.util.ErrorHandling;
 import org.apache.jackrabbit.core.persistence.util.FileSystemBLOBStore;
+import org.apache.jackrabbit.core.persistence.util.NodeInfo;
 import org.apache.jackrabbit.core.persistence.util.NodePropBundle;
 import org.apache.jackrabbit.core.persistence.util.Serializer;
 import org.apache.jackrabbit.core.state.ItemStateException;
@@ -55,7 +59,7 @@ import org.slf4j.LoggerFactory;
  * <code>PersistenceManager</code> for Jackrabbit that keeps all data in memory
  * and that is capable of storing and loading its contents using a simple custom
  * binary serialization format (see {@link Serializer}).
- * <p/>
+ * <p>
  * It is configured through the following properties:
  * <ul>
  * <li><code>initialCapacity</code>: initial capacity of the hash map used to store the data</li>
@@ -68,7 +72,7 @@ import org.slf4j.LoggerFactory;
  * <li><code>minBlobSize</code> use blob store for binaries properties larger 
  * than minBlobSite (bytes). Default is 4096.</li>
  * </ul>
- * <p/>
+ * <p>
  * <b>Please note that this class should only be used for testing purposes.</b>
  *
  */
@@ -380,7 +384,7 @@ public class InMemBundlePersistenceManager extends AbstractBundlePersistenceMana
         }
         super.init(context);
         // initialize mem stores
-        bundleStore = new HashMap<NodeId, byte[]>(initialCapacity, loadFactor);
+        bundleStore = new LinkedHashMap<NodeId, byte[]>(initialCapacity, loadFactor);
         refsStore = new HashMap<NodeId, byte[]>(initialCapacity, loadFactor);
 
         // Choose a FileSystem for the BlobStore based on whether data is persistent or not 
@@ -489,9 +493,21 @@ public class InMemBundlePersistenceManager extends AbstractBundlePersistenceMana
     /**
      * {@inheritDoc}
      */
-    public Iterable<NodeId> getAllNodeIds(NodeId after, int maxCount) throws ItemStateException, RepositoryException {
-        // ignore after and count parameters.
-        return bundleStore.keySet();
+    public List<NodeId> getAllNodeIds(NodeId after, int maxCount) throws ItemStateException, RepositoryException {
+        final List<NodeId> result = new ArrayList<NodeId>();
+        boolean add = after == null;
+        int count = 0;
+        for (NodeId nodeId : bundleStore.keySet()) {
+            if (add) {
+                result.add(nodeId);
+                if (++count == maxCount) {
+                    break;
+                }
+            } else {
+                add = nodeId.equals(after);
+            }
+        }
+        return result;
     }
 
     /**
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/mem/InMemPersistenceManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/mem/InMemPersistenceManager.java
index 81352fe..f758c2d 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/mem/InMemPersistenceManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/mem/InMemPersistenceManager.java
@@ -54,7 +54,7 @@ import java.util.Map;
  * <code>PersistenceManager</code> for Jackrabbit that keeps all data in memory
  * and that is capable of storing and loading its contents using a simple custom
  * binary serialization format (see {@link Serializer}).
- * <p/>
+ * <p>
  * It is configured through the following properties:
  * <ul>
  * <li><code>initialCapacity</code>: initial capacity of the hash map used to store the data</li>
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/BundleDbPersistenceManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/BundleDbPersistenceManager.java
index 5744c23..9077302 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/BundleDbPersistenceManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/BundleDbPersistenceManager.java
@@ -26,11 +26,9 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Types;
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Random;
-import java.util.Set;
+import java.util.Map;
 
 import javax.jcr.RepositoryException;
 import javax.sql.DataSource;
@@ -43,15 +41,11 @@ import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.id.PropertyId;
 import org.apache.jackrabbit.core.persistence.PMContext;
 import org.apache.jackrabbit.core.persistence.bundle.AbstractBundlePersistenceManager;
-import org.apache.jackrabbit.core.persistence.check.ConsistencyChecker;
-import org.apache.jackrabbit.core.persistence.check.ConsistencyReport;
-import org.apache.jackrabbit.core.persistence.check.ConsistencyReportImpl;
-import org.apache.jackrabbit.core.persistence.check.ReportItem;
-import org.apache.jackrabbit.core.persistence.check.ReportItemImpl;
 import org.apache.jackrabbit.core.persistence.util.BLOBStore;
 import org.apache.jackrabbit.core.persistence.util.BundleBinding;
 import org.apache.jackrabbit.core.persistence.util.ErrorHandling;
 import org.apache.jackrabbit.core.persistence.util.FileSystemBLOBStore;
+import org.apache.jackrabbit.core.persistence.util.NodeInfo;
 import org.apache.jackrabbit.core.persistence.util.NodePropBundle;
 import org.apache.jackrabbit.core.persistence.util.Serializer;
 import org.apache.jackrabbit.core.state.ChangeLog;
@@ -65,14 +59,13 @@ import org.apache.jackrabbit.core.util.db.ConnectionHelper;
 import org.apache.jackrabbit.core.util.db.DatabaseAware;
 import org.apache.jackrabbit.core.util.db.DbUtility;
 import org.apache.jackrabbit.core.util.db.StreamWrapper;
-import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
  * This is a generic persistence manager that stores the {@link NodePropBundle}s
  * in a database.
- * <p/>
+ * <p>
  * Configuration:<br>
  * <ul>
  * <li><param name="{@link #setBundleCacheSize(String) bundleCacheSize}" value="8"/>
@@ -91,7 +84,7 @@ import org.slf4j.LoggerFactory;
  * </ul>
  */
 public class BundleDbPersistenceManager
-        extends AbstractBundlePersistenceManager implements DatabaseAware, ConsistencyChecker {
+        extends AbstractBundlePersistenceManager implements DatabaseAware {
 
     /** the default logger */
     private static Logger log = LoggerFactory.getLogger(BundleDbPersistenceManager.class);
@@ -151,6 +144,8 @@ public class BundleDbPersistenceManager
     protected String bundleDeleteSQL;
     protected String bundleSelectAllIdsFromSQL;
     protected String bundleSelectAllIdsSQL;
+    protected String bundleSelectAllBundlesFromSQL;
+    protected String bundleSelectAllBundlesSQL;
 
     // SQL statements for NodeReference management
     protected String nodeReferenceInsertSQL;
@@ -320,7 +315,7 @@ public class BundleDbPersistenceManager
      * @deprecated
      * This method is deprecated; {@link setDatabaseType} should be used instead.
      *
-     * @param database type name
+     * @param databaseType database type name
      */
     public void setSchema(String databaseType) {
         this.databaseType = databaseType;
@@ -331,7 +326,7 @@ public class BundleDbPersistenceManager
      * the respective .ddl resource in order to create the required schema
      * objects.
      *
-     * @param database type name
+     * @param databaseType database type name
      */
     public void setDatabaseType(String databaseType) {
         this.databaseType = databaseType;
@@ -506,7 +501,7 @@ public class BundleDbPersistenceManager
             }
             failures++;
             log.error("Failed to persist ChangeLog (stacktrace on DEBUG log level), blockOnConnectionLoss = "
-                    + blockOnConnectionLoss, lastException);
+                    + blockOnConnectionLoss + ": " + lastException);
             log.debug("Failed to persist ChangeLog", lastException);
             if (blockOnConnectionLoss || failures <= 1) { // if we're going to try again
                 try {
@@ -706,250 +701,6 @@ public class BundleDbPersistenceManager
         return new DbBlobStore();
     }
 
-    private void addMessage(Set<ReportItem> reports, NodeId id, String message) {
-        if (reports != null) {
-            reports.add(new ReportItemImpl(id.toString(), message));
-        }
-    }
-
-    /**
-     * Checks a single bundle for inconsistencies, ie. inexistent child nodes
-     * and inexistent parents.
-     *
-     * @param id node id for the bundle to check
-     * @param bundle the bundle to check
-     * @param fix if <code>true</code>, repair things that can be repaired
-     * @param modifications if <code>fix == true</code>, collect the repaired
-     * {@linkplain NodePropBundle bundles} here
-     */
-    protected void checkBundleConsistency(NodeId id, NodePropBundle bundle,
-                                          boolean fix, Collection<NodePropBundle> modifications,
-                                          Set<ReportItem> reports) {
-        //log.info(name + ": checking bundle '" + id + "'");
-
-        // skip all system nodes except root node
-        if (id.toString().endsWith("babecafebabe")
-                && !id.toString().equals("cafebabe-cafe-babe-cafe-babecafebabe")) {
-            return;
-        }
-
-        // look at the node's children
-        Collection<NodePropBundle.ChildNodeEntry> missingChildren = new ArrayList<NodePropBundle.ChildNodeEntry>();
-        for (NodePropBundle.ChildNodeEntry entry : bundle.getChildNodeEntries()) {
-
-            // skip check for system nodes (root, system root, version storage, node types)
-            if (entry.getId().toString().endsWith("babecafebabe")) {
-                continue;
-            }
-
-            try {
-                // analyze child node bundles
-                NodePropBundle child = loadBundle(entry.getId());
-                String message = null;
-                if (child == null) {
-                    message = "NodeState '" + id + "' references inexistent child" + " '"
-                            + entry.getName() + "' with id " + "'" + entry.getId() + "'";
-                    log.error(message);
-                    missingChildren.add(entry);
-                } else {
-                    NodeId cp = child.getParentId();
-                    if (cp == null) {
-                        message = "ChildNode has invalid parent id: <null>";
-                        log.error(message);
-                    } else if (!cp.equals(id)) {
-                        message = "ChildNode has invalid parent id: '" + cp + "' (instead of '" + id + "')";
-                        log.error(message);
-                    }
-                }
-                if (message != null) {
-                    addMessage(reports, id, message);
-                }
-            } catch (ItemStateException e) {
-                // problem already logged (loadBundle called with logDetailedErrors=true)
-                addMessage(reports, id, e.getMessage());
-            }
-        }
-        // remove child node entry (if fixing is enabled)
-        if (fix && !missingChildren.isEmpty()) {
-            for (NodePropBundle.ChildNodeEntry entry : missingChildren) {
-                bundle.getChildNodeEntries().remove(entry);
-            }
-            modifications.add(bundle);
-        }
-
-        // check parent reference
-        NodeId parentId = bundle.getParentId();
-        try {
-            // skip root nodes (that point to itself)
-            if (parentId != null && !id.toString().endsWith("babecafebabe")) {
-                NodePropBundle parentBundle = loadBundle(parentId);
-                
-                if (parentBundle == null) {
-                    String message = "NodeState '" + id + "' references inexistent parent id '" + parentId + "'";
-                    log.error(message);
-                    addMessage(reports, id, message);
-                }
-                else {
-                    boolean found = false;
-
-                    for (NodePropBundle.ChildNodeEntry entry : parentBundle.getChildNodeEntries()) {
-                        if (entry.getId().equals(id)){
-                            found = true;
-                            break;
-                        }
-                    }
-
-                    if (!found) {
-                        String message = "NodeState '" + id + "' is not referenced by its parent node '" + parentId + "'";
-                        log.error(message);
-                        addMessage(reports, id, message);
-
-                        int l = (int) System.currentTimeMillis();
-                        int r = new Random().nextInt();
-                        int n = l + r;
-                        String nodeName = Integer.toHexString(n);
-                        parentBundle.addChildNodeEntry(NameFactoryImpl
-                                .getInstance().create("{}" + nodeName), id);
-                        log.info("NodeState '" + id + "' adds itself to its parent node '" + parentId + "' with a new name '" + nodeName + "'");
-                        modifications.add(parentBundle);
-                    }
-                }
-            }
-        } catch (ItemStateException e) {
-            String message = "Error reading node '" + parentId + "' (parent of '" + id + "'): " + e;
-            log.error(message);
-            addMessage(reports, id, message);
-        }
-    }
-
-    public ConsistencyReport check(String[] uuids, boolean recursive,
-            boolean fix) throws RepositoryException {
-
-        Set<ReportItem> reports = new HashSet<ReportItem>();
-
-        long tstart = System.currentTimeMillis();
-        int total = internalCheckConsistency(uuids, recursive, fix, reports);
-        long elapsed = System.currentTimeMillis() - tstart;
-
-        return new ConsistencyReportImpl(total, elapsed, reports);
-    }
-
-    public void checkConsistency(String[] uuids, boolean recursive, boolean fix) {
-        try {
-            internalCheckConsistency(uuids, recursive, fix, null);
-        }
-        catch (RepositoryException ex) {
-            log.error("While running consistency check.", ex);
-        }
-    }
-    
-    private int internalCheckConsistency(String[] uuids, boolean recursive, boolean fix, Set<ReportItem> reports) throws RepositoryException {
-        int count = 0;
-        int total = 0;
-        Collection<NodePropBundle> modifications = new ArrayList<NodePropBundle>();        
-
-        if (uuids == null) {
-            total = getNumberOfNodeIds();
-            
-            try {
-                Iterable<NodeId> allIds = getAllNodeIds(null, 0);
-
-                for (NodeId id : allIds) {
-                    try {
-                        // parse and check bundle
-                        NodePropBundle bundle = loadBundle(id);
-                        if (bundle == null) {
-                            log.error("No bundle found for id '" + id + "'");
-                        } else {
-                            checkBundleConsistency(id, bundle, fix, modifications, reports);
-
-                            count++;
-                            if (count % 1000 == 0) {
-                                log.info(name + ": checked " + count + "/" + total + " bundles...");
-                            }
-                        }
-                    } catch (ItemStateException e) {
-                        // problem already logged (loadBundle called with
-                        // logDetailedErrors=true)
-                    }
-
-                }
-            } catch (ItemStateException ex) {
-                throw new RepositoryException("getting nodeIds", ex);
-            } finally {
-                total = count;
-            }
-        } else {
-            // check only given uuids, handle recursive flag
-
-            // 1) convert uuid array to modifiable list
-            // 2) for each uuid do
-            //     a) load node bundle
-            //     b) check bundle, store any bundle-to-be-modified in collection
-            //     c) if recursive, add child uuids to list of uuids
-
-            List<NodeId> idList = new ArrayList<NodeId>(uuids.length);
-            // convert uuid string array to list of UUID objects
-            for (int i = 0; i < uuids.length; i++) {
-                try {
-                    idList.add(new NodeId(uuids[i]));
-                } catch (IllegalArgumentException e) {
-                    log.error("Invalid id for consistency check, skipping: '" + uuids[i] + "': " + e);
-                }
-            }
-            
-            // iterate over UUIDs (including ones that are newly added inside the loop!)
-            for (int i = 0; i < idList.size(); i++) {
-                NodeId id = idList.get(i);
-                try {
-                    // load the node from the database
-                    NodePropBundle bundle = loadBundle(id);
-
-                    if (bundle == null) {
-                        log.error("No bundle found for id '" + id + "'");
-                    }
-                    else {
-                        checkBundleConsistency(id, bundle, fix, modifications, reports);
-
-                        if (recursive) {
-                            for (NodePropBundle.ChildNodeEntry entry : bundle.getChildNodeEntries()) {
-                                idList.add(entry.getId());
-                            }
-                        }
-
-                        count++;
-                        if (count % 1000 == 0) {
-                            log.info(name + ": checked " + count + "/" + idList.size() + " bundles...");
-                        }
-                    }
-                } catch (ItemStateException e) {
-                    // problem already logged (loadBundle called with logDetailedErrors=true)
-                }
-            }
-
-            total = idList.size();
-        }
-
-        // repair collected broken bundles
-        if (fix && !modifications.isEmpty()) {
-            log.info(name + ": Fixing " + modifications.size() + " inconsistent bundle(s)...");
-            for (NodePropBundle bundle : modifications) {
-                try {
-                    log.info(name + ": Fixing bundle '" + bundle.getId() + "'");
-                    bundle.markOld(); // use UPDATE instead of INSERT
-                    storeBundle(bundle);
-                    evictBundle(bundle.getId());
-                } catch (ItemStateException e) {
-                    log.error(name + ": Error storing fixed bundle: " + e);
-                }
-            }
-        }
-
-        log.info(name + ": checked " + count + "/" + total + " bundles.");
-
-        return total;
-    }
-
     /**
      * {@inheritDoc}
      */
@@ -1020,20 +771,53 @@ public class BundleDbPersistenceManager
         return params.toArray();
     }
 
-    private synchronized int getNumberOfNodeIds() throws RepositoryException {
+    /**
+     * {@inheritDoc}
+     */
+    public synchronized List<NodeId> getAllNodeIds(NodeId bigger, int maxCount)
+            throws ItemStateException, RepositoryException {
         ResultSet rs = null;
         try {
-            String sql = "select count(*) from " + schemaObjectPrefix + "BUNDLE";
-            rs = conHelper.exec(sql, new Object[0], false, 0);
-
-            if (!rs.next()) {
-                String message = "Could not retrieve total number of bundles: empty result set.";
-                log.error(message);
-                throw new RepositoryException(message);
+            String sql = bundleSelectAllIdsSQL;
+            NodeId lowId = null;
+            Object[] keys = new Object[0];
+            if (bigger != null) {
+                sql = bundleSelectAllIdsFromSQL;
+                lowId = bigger;
+                keys = getKey(bigger);
             }
-            return rs.getInt(1);
-        } catch (SQLException ex) {
-            throw new RepositoryException("Could not retrieve total number of bundles", ex);
+            if (getStorageModel() == SM_LONGLONG_KEYS  && maxCount > 0) {
+                // get some more rows, in case the first row is smaller
+                // only required for SM_LONGLONG_KEYS
+                // probability is very low to get get the wrong first key, < 1 : 2^64
+                // see also bundleSelectAllIdsFrom SQL statement
+                maxCount += 10;
+            }
+            rs = conHelper.exec(sql, keys, false, maxCount);
+            ArrayList<NodeId> result = new ArrayList<NodeId>();
+            while ((maxCount == 0 || result.size() < maxCount) && rs.next()) {
+                NodeId current;
+                if (getStorageModel() == SM_BINARY_KEYS) {
+                    current = new NodeId(rs.getBytes(1));
+                } else {
+                    long high = rs.getLong(1);
+                    long low = rs.getLong(2);
+                    current = new NodeId(high, low);
+                    if (lowId != null) {
+                        // skip the keys that are smaller or equal (see above, maxCount += 10)
+                        // only required for SM_LONGLONG_KEYS
+                        if (current.compareTo(lowId) <= 0) {
+                            continue;
+                        }
+                    }
+                }
+                result.add(current);
+            }
+            return result;
+        } catch (SQLException e) {
+            String msg = "getAllNodeIds failed.";
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
         } finally {
             DbUtility.close(rs);
         }
@@ -1042,19 +826,19 @@ public class BundleDbPersistenceManager
     /**
      * {@inheritDoc}
      */
-    public synchronized Iterable<NodeId> getAllNodeIds(NodeId bigger, int maxCount)
-            throws ItemStateException, RepositoryException {
+    @Override
+    public synchronized Map<NodeId, NodeInfo> getAllNodeInfos(NodeId bigger, int maxCount) throws ItemStateException {
         ResultSet rs = null;
         try {
-            String sql = bundleSelectAllIdsSQL;
+            String sql = bundleSelectAllBundlesSQL;
             NodeId lowId = null;
             Object[] keys = new Object[0];
             if (bigger != null) {
-                sql = bundleSelectAllIdsFromSQL;
+                sql = bundleSelectAllBundlesFromSQL;
                 lowId = bigger;
                 keys = getKey(bigger);
             }
-            if (maxCount > 0) {
+            if (getStorageModel() == SM_LONGLONG_KEYS && maxCount > 0) {
                 // get some more rows, in case the first row is smaller
                 // only required for SM_LONGLONG_KEYS
                 // probability is very low to get get the wrong first key, < 1 : 2^64
@@ -1062,7 +846,7 @@ public class BundleDbPersistenceManager
                 maxCount += 10;
             }
             rs = conHelper.exec(sql, keys, false, maxCount);
-            ArrayList<NodeId> result = new ArrayList<NodeId>();
+            Map<NodeId, NodeInfo> result = new LinkedHashMap<NodeId, NodeInfo>(maxCount);
             while ((maxCount == 0 || result.size() < maxCount) && rs.next()) {
                 NodeId current;
                 if (getStorageModel() == SM_BINARY_KEYS) {
@@ -1072,13 +856,15 @@ public class BundleDbPersistenceManager
                     long low = rs.getLong(2);
                     current = new NodeId(high, low);
                 }
-                if (lowId != null) {
+                if (getStorageModel() == SM_LONGLONG_KEYS && lowId != null) {
                     // skip the keys that are smaller or equal (see above, maxCount += 10)
                     if (current.compareTo(lowId) <= 0) {
                         continue;
                     }
                 }
-                result.add(current);
+                NodePropBundle bundle = readBundle(current, rs, getStorageModel() == SM_LONGLONG_KEYS ? 3 : 2);
+                NodeInfo nodeInfo = new NodeInfo(bundle);
+                result.put(nodeInfo.getId(), nodeInfo);
             }
             return result;
         } catch (SQLException e) {
@@ -1099,17 +885,20 @@ public class BundleDbPersistenceManager
             ResultSet rs =
                 conHelper.exec(bundleSelectSQL, getKey(id), false, 0);
             try {
-                if (rs.next()) {
+                if (rs != null && rs.next()) {
                     return readBundle(id, rs, 1);
                 } else {
                     return null;
                 }
             } finally {
-                rs.close();
+            	if (rs != null) {
+            		rs.close();
+            	}
             }
         } catch (SQLException e) {
-            String msg = "failed to read bundle: " + id + ": " + e;
+        	String msg = "failed to read bundle (stacktrace on DEBUG log level): " + id + ": " + e; 
             log.error(msg);
+            log.debug("failed to read bundle: " + id, e);
             throw new ItemStateException(msg, e);
         }
     }
@@ -1330,8 +1119,10 @@ public class BundleDbPersistenceManager
             nodeReferenceSelectSQL = "select REFS_DATA from " + schemaObjectPrefix + "REFS where NODE_ID = ?";
             nodeReferenceDeleteSQL = "delete from " + schemaObjectPrefix + "REFS where NODE_ID = ?";
 
-            bundleSelectAllIdsSQL = "select NODE_ID from " + schemaObjectPrefix + "BUNDLE";
+            bundleSelectAllIdsSQL = "select NODE_ID from " + schemaObjectPrefix + "BUNDLE ORDER BY NODE_ID";
             bundleSelectAllIdsFromSQL = "select NODE_ID from " + schemaObjectPrefix + "BUNDLE WHERE NODE_ID > ? ORDER BY NODE_ID";
+            bundleSelectAllBundlesSQL = "select NODE_ID, BUNDLE_DATA from " + schemaObjectPrefix + "BUNDLE ORDER BY NODE_ID";
+            bundleSelectAllBundlesFromSQL = "select NODE_ID, BUNDLE_DATA from " + schemaObjectPrefix + "BUNDLE WHERE NODE_ID > ? ORDER BY NODE_ID";
         } else {
             bundleInsertSQL = "insert into " + schemaObjectPrefix + "BUNDLE (BUNDLE_DATA, NODE_ID_HI, NODE_ID_LO) values (?, ?, ?)";
             bundleUpdateSQL = "update " + schemaObjectPrefix + "BUNDLE set BUNDLE_DATA = ? where NODE_ID_HI = ? and NODE_ID_LO = ?";
@@ -1347,13 +1138,24 @@ public class BundleDbPersistenceManager
             nodeReferenceSelectSQL = "select REFS_DATA from " + schemaObjectPrefix + "REFS where NODE_ID_HI = ? and NODE_ID_LO = ?";
             nodeReferenceDeleteSQL = "delete from " + schemaObjectPrefix + "REFS where NODE_ID_HI = ? and NODE_ID_LO = ?";
 
-            bundleSelectAllIdsSQL = "select NODE_ID_HI, NODE_ID_LO from " + schemaObjectPrefix + "BUNDLE";
+            bundleSelectAllIdsSQL = "select NODE_ID_HI, NODE_ID_LO from " + schemaObjectPrefix 
+                + "BUNDLE ORDER BY NODE_ID_HI, NODE_ID_LO";
             // need to use HI and LO parameters
             // this is not the exact statement, but not all databases support WHERE (NODE_ID_HI, NODE_ID_LOW) >= (?, ?)
             bundleSelectAllIdsFromSQL =
                 "select NODE_ID_HI, NODE_ID_LO from " + schemaObjectPrefix + "BUNDLE"
                 + " WHERE (NODE_ID_HI >= ?) AND (? IS NOT NULL)"
                 + " ORDER BY NODE_ID_HI, NODE_ID_LO";
+
+            bundleSelectAllBundlesSQL = "select NODE_ID_HI, NODE_ID_LO, BUNDLE_DATA from " + schemaObjectPrefix
+                    + "BUNDLE ORDER BY NODE_ID_HI, NODE_ID_LO";
+            // need to use HI and LO parameters
+            // this is not the exact statement, but not all databases support WHERE (NODE_ID_HI, NODE_ID_LOW) >= (?, ?)
+            bundleSelectAllBundlesFromSQL =
+                    "select NODE_ID_HI, NODE_ID_LO, BUNDLE_DATA from " + schemaObjectPrefix + "BUNDLE"
+                            + " WHERE (NODE_ID_HI >= ?) AND (? IS NOT NULL)"
+                            + " ORDER BY NODE_ID_HI, NODE_ID_LO";
+
         }
 
     }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/DbNameIndex.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/DbNameIndex.java
index c6a9938..a1a0462 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/DbNameIndex.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/DbNameIndex.java
@@ -27,10 +27,10 @@ import org.apache.jackrabbit.core.util.db.DbUtility;
 /**
  * Implements a {@link StringIndex} that stores and retrieves the names from a
  * table in a database.
- * <p/>
+ * <p>
  * Note that this class is not threadsafe by itself. it needs to be synchronized
  * by the using application.
- * <p/>
+ * <p>
  * Due to a bug with oracle that treats empty strings a null values
  * (see JCR-815), all empty strings are replaced by a ' '. since names never
  * start with a space, this it not problematic yet.
@@ -50,7 +50,7 @@ public class DbNameIndex implements StringIndex {
 
     /**
      * Creates a new index that is stored in a db.
-     * @param conHelper the {@link ConnectionHelper}
+     * @param conHlpr the {@link ConnectionHelper}
      * @param schemaObjectPrefix the prefix for table names
      * @throws SQLException if the statements cannot be prepared.
      */
@@ -63,7 +63,6 @@ public class DbNameIndex implements StringIndex {
     /**
      * Inits this index and prepares the statements.
      *
-     * @param con the jdbc connection
      * @param schemaObjectPrefix the prefix for table names
      * @throws SQLException if the statements cannot be prepared.
      */
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/DerbyPersistenceManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/DerbyPersistenceManager.java
index 04c89b6..4399fb5 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/DerbyPersistenceManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/DerbyPersistenceManager.java
@@ -27,7 +27,7 @@ import javax.sql.DataSource;
 
 /**
  * Extends the {@link BundleDbPersistenceManager} by derby specific code.
- * <p/>
+ * <p>
  * Configuration:<br>
  * <ul>
  * <li><param name="{@link #setBundleCacheSize(String) bundleCacheSize}" value="8"/>
@@ -81,27 +81,27 @@ public class DerbyPersistenceManager extends BundleDbPersistenceManager {
      * pages of user data (or nine pages of total disk use, one is used for
      * overhead) have been allocated. Then it will grow by eight pages at a time
      * if possible.
-     * <p/>
+     * <p>
      * A Derby table or index can be created with a number of pages already
      * pre-allocated. To do so, specify the property prior to the CREATE TABLE
      * or CREATE INDEX statement.
-     * <p/>
+     * <p>
      * Define the number of user pages the table or index is to be created with.
      * The purpose of this property is to preallocate a table or index of
      * reasonable size if the user expects that a large amount of data will be
      * inserted into the table or index. A table or index that has the
      * pre-allocated pages will enjoy a small performance improvement over a
      * table or index that has no pre-allocated pages when the data are loaded.
-     * <p/>
+     * <p>
      * The total desired size of the table or index should be
-     * <p/>
+     * <p>
      * <strong>(1+derby.storage.initialPages) * derby.storage.pageSize bytes.</strong>
-     * <p/>
+     * <p>
      * When you create a table or an index after setting this property, Derby
      * attempts to preallocate the requested number of user pages. However, the
      * operations do not fail even if they are unable to preallocate the
      * requested number of pages, as long as they allocate at least one page.
-     * <p/>
+     * <p>
      * Default is <code>16</code>
      *
      * @param derbyStorageInitialPages the number of initial pages
@@ -129,7 +129,7 @@ public class DerbyPersistenceManager extends BundleDbPersistenceManager {
      * space at the time of insertion minimizes row overflow due to updates,
      * but it can result in wasted space. Set the property prior to issuing the
      * CREATE TABLE statement.
-     * <p/>
+     * <p>
      * Default is <code>256</code>
      *
      * @param derbyStorageMinimumRecordSize the minimum record size
@@ -162,10 +162,10 @@ public class DerbyPersistenceManager extends BundleDbPersistenceManager {
      * application as well). For example, using the default page size of 4K, a
      * page cache size of 2000 pages will require at least 8 MB of memory (and
      * probably more, given the overhead).
-     * <p/>
+     * <p>
      * The minimum value is 40 pages. If you specify a lower value, Derby uses
      * the default value.
-     * <p/>
+     * <p>
      * Default is <code>1024</code> (which gives about 16mb memory usage given
      * the default of 16384 as page size).
      *
@@ -194,10 +194,10 @@ public class DerbyPersistenceManager extends BundleDbPersistenceManager {
      * threshold, no new rows are allowed on the page. This reserved space is
      * used only for rows that increase in size when updated, not for new
      * inserts. Set this property prior to issuing the CREATE TABLE statement.
-     * <p/>
+     * <p>
      * Regardless of the value of derby.storage.pageReservedSpace, an empty page
      * always accepts at least one row.
-     * <p/>
+     * <p>
      * Default is <code>20%</code>
      *
      * @param derbyStoragePageReservedSpace the page reserved space
@@ -221,7 +221,7 @@ public class DerbyPersistenceManager extends BundleDbPersistenceManager {
      * the following values: 4096, 8192, 16384, or 32768. Set this property
      * prior to issuing the CREATE TABLE or CREATE INDEX statement. This value
      * will be used for the lifetime of the newly created conglomerates.
-     * <p/>
+     * <p>
      * Default is <code>16384</code>
      *
      * @param derbyStoragePageSize the storage page size
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/H2PersistenceManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/H2PersistenceManager.java
index 4dcfd90..dbfd68b 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/H2PersistenceManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/H2PersistenceManager.java
@@ -20,7 +20,7 @@ import org.apache.jackrabbit.core.persistence.PMContext;
 
 /**
  * Extends the {@link BundleDbPersistenceManager} by H2 specific code.
- * <p/>
+ * <p>
  * Configuration:
  * <pre>
  * <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/MySqlPersistenceManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/MySqlPersistenceManager.java
index bcaab08..41207fb 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/MySqlPersistenceManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/MySqlPersistenceManager.java
@@ -20,7 +20,7 @@ import org.apache.jackrabbit.core.persistence.PMContext;
 
 /**
  * Extends the {@link BundleDbPersistenceManager} by mysql specific code.
- * <p/>
+ * <p>
  * Configuration:<br>
  * <ul>
  * <li><param name="{@link #setBundleCacheSize(String) bundleCacheSize}" value="8"/>
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/NGKDbNameIndex.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/NGKDbNameIndex.java
index fb80c40..c022419 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/NGKDbNameIndex.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/NGKDbNameIndex.java
@@ -51,7 +51,7 @@ public class NGKDbNameIndex extends DbNameIndex {
 
     /**
      * Inserts a string into the database and returns the new index.
-     * <p/>
+     * <p>
      * Instead of using the {@link Statement#RETURN_GENERATED_KEYS} feature, the
      * newly inserted index is retrieved by a 2nd select statement.
      *
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/OraclePersistenceManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/OraclePersistenceManager.java
index 13a5f6a..2e2b5b8 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/OraclePersistenceManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/OraclePersistenceManager.java
@@ -102,7 +102,7 @@ public class OraclePersistenceManager extends BundleDbPersistenceManager {
     
     /**
      * Sets the Oracle tablespace for indexes.
-     * @param tablespace the Oracle tablespace for indexes.
+     * @param tablespaceName the Oracle tablespace for indexes.
      */
     public void setIndexTablespace(String tablespaceName) {
         this.indexTablespace = this.buildTablespaceClause(tablespaceName);
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/PostgreSQLNameIndex.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/PostgreSQLNameIndex.java
index 9a5fc8a..0d13283 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/PostgreSQLNameIndex.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/PostgreSQLNameIndex.java
@@ -39,7 +39,6 @@ public class PostgreSQLNameIndex extends DbNameIndex {
     /**
      * Inits this index and prepares the statements.
      *
-     * @param con the jdbc connection
      * @param schemaObjectPrefix the prefix for table names
      * @throws SQLException if the statements cannot be prepared.
      */
@@ -53,7 +52,7 @@ public class PostgreSQLNameIndex extends DbNameIndex {
 
     /**
      * Inserts a string into the database and returns the new index.
-     * <p/>
+     * <p>
      * Instead of using the {@link java.sql.Statement#RETURN_GENERATED_KEYS}
      * feature, the newly inserted index is retrieved by a 2nd select statement.
      *
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/PostgreSQLPersistenceManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/PostgreSQLPersistenceManager.java
index b127c04..064260e 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/PostgreSQLPersistenceManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/PostgreSQLPersistenceManager.java
@@ -26,7 +26,7 @@ import org.apache.jackrabbit.core.util.db.PostgreSQLConnectionHelper;
 
 /**
  * Extends the {@link BundleDbPersistenceManager} by PostgreSQL specific code.
- * <p/>
+ * <p>
  * Configuration:<br>
  * <ul>
  * <li><param name="{@link #setBundleCacheSize(String) bundleCacheSize}" value="8"/>
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleReader.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleReader.java
index 05fd5f1..c9ad9de 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleReader.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleReader.java
@@ -18,6 +18,7 @@ package org.apache.jackrabbit.core.persistence.util;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.apache.commons.io.IOExceptionWithCause;
 import org.apache.commons.io.input.CountingInputStream;
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.id.PropertyId;
@@ -338,7 +339,8 @@ class BundleReader {
         String[] blobIds = new String[count];
         for (int i = 0; i < count; i++) {
             InternalValue val;
-            switch (entry.getType()) {
+            int type = entry.getType();
+            switch (type) {
                 case PropertyType.BINARY:
                     int size = in.readInt();
                     if (size == BundleBinding.BINARY_IN_DATA_STORE) {
@@ -360,7 +362,7 @@ class BundleReader {
                                 throw e;
                             }
                         } catch (Exception e) {
-                            throw new IOException("Unable to create property value: " + e.toString());
+                            throw new IOExceptionWithCause("Unable to create property value: " + e.toString(), e);
                         }
                     } else {
                         // short values into memory
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleWriter.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleWriter.java
index 819f65a..7724f17 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleWriter.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleWriter.java
@@ -29,8 +29,8 @@ import java.util.GregorianCalendar;
 import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 
+import org.apache.commons.io.IOExceptionWithCause;
 import org.apache.commons.io.IOUtils;
-import org.apache.jackrabbit.core.data.DataStore;
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.value.InternalValue;
 import org.apache.jackrabbit.core.persistence.util.NodePropBundle.ChildNodeEntry;
@@ -185,7 +185,9 @@ class BundleWriter {
         InternalValue[] values = state.getValues();
 
         int type = state.getType();
-        assert 0 <= type && type <= 0x0f;
+        if (type < 0 || type > 0xf) {
+            throw new IOException("Illegal property type " + type);
+        }
         if (state.isMultiValued()) {
             int len = values.length + 1;
             if (len < 0x0f) {
@@ -195,7 +197,11 @@ class BundleWriter {
                 writeVarInt(len - 0x0f);
             }
         } else {
-            assert values.length == 1;
+            if (values.length != 1) {
+                throw new IOException(
+                        "Single values property with " + values.length + " values: " + 
+                        state.getName());
+            }
             out.writeByte(type);
         }
 
@@ -204,31 +210,24 @@ class BundleWriter {
         // values
         for (int i = 0; i < values.length; i++) {
             InternalValue val = values[i];
-            switch (state.getType()) {
+            switch (type) {
                 case PropertyType.BINARY:
                     try {
                         long size = val.getLength();
-                        DataStore dataStore = binding.dataStore;
-                        if (dataStore != null) {
-                            int maxMemorySize = dataStore.getMinRecordLength() - 1;
-                            if (size < maxMemorySize) {
-                                writeSmallBinary(val, state, i);
-                            } else {
-                                out.writeInt(BundleBinding.BINARY_IN_DATA_STORE);
-                                val.store(dataStore);
-                                writeString(val.toString());
-                            }
-                            break;
-                        }
-                        // special handling required for binary value:
-                        // spool binary value to file in blob store
-                        if (size < 0) {
+                        if (val.isInDataStore()) {
+                            out.writeInt(BundleBinding.BINARY_IN_DATA_STORE);
+                            writeString(val.toString());
+                        } else if (binding.dataStore != null) {
+                            writeSmallBinary(val, state, i);
+                        } else if (size < 0) {
                             log.warn("Blob has negative size. Potential loss of data. "
                                     + "id={} idx={}", state.getId(), String.valueOf(i));
                             out.writeInt(0);
                             values[i] = InternalValue.create(new byte[0]);
                             val.discard();
                         } else if (size > binding.getMinBlobSize()) {
+                            // special handling required for binary value:
+                            // spool binary value to file in blob store
                             out.writeInt(BundleBinding.BINARY_IN_BLOB_STORE);
                             String blobId = state.getBlobId(i);
                             if (blobId == null) {
@@ -246,7 +245,7 @@ class BundleWriter {
                                     String msg = "Error while storing blob. id="
                                             + state.getId() + " idx=" + i + " size=" + size;
                                     log.error(msg, e);
-                                    throw new IOException(msg);
+                                    throw new IOExceptionWithCause(msg, e);
                                 }
                                 try {
                                     // replace value instance with value
@@ -277,47 +276,42 @@ class BundleWriter {
                         String msg = "Error while storing blob. id="
                             + state.getId() + " idx=" + i + " value=" + val;
                         log.error(msg, e);
-                        throw new IOException(msg);
+                        throw new IOExceptionWithCause(msg, e);
                     }
                     break;
                 case PropertyType.DOUBLE:
                     try {
                         out.writeDouble(val.getDouble());
                     } catch (RepositoryException e) {
-                        // should never occur
-                        throw new IOException("Unexpected error while writing DOUBLE value.");
+                        throw convertToIOException(type, e);
                     }
                     break;
                 case PropertyType.DECIMAL:
                     try {
                         writeDecimal(val.getDecimal());
                     } catch (RepositoryException e) {
-                        // should never occur
-                        throw new IOException("Unexpected error while writing DECIMAL value.");
+                        throw convertToIOException(type, e);
                     }
                     break;
                 case PropertyType.LONG:
                     try {
                         writeVarLong(val.getLong());
                     } catch (RepositoryException e) {
-                        // should never occur
-                        throw new IOException("Unexpected error while writing LONG value.");
+                        throw convertToIOException(type, e);
                     }
                     break;
                 case PropertyType.BOOLEAN:
                     try {
                         out.writeBoolean(val.getBoolean());
                     } catch (RepositoryException e) {
-                        // should never occur
-                        throw new IOException("Unexpected error while writing BOOLEAN value.");
+                        throw convertToIOException(type, e);
                     }
                     break;
                 case PropertyType.NAME:
                     try {
                         writeName(val.getName());
                     } catch (RepositoryException e) {
-                        // should never occur
-                        throw new IOException("Unexpected error while writing NAME value.");
+                        throw convertToIOException(type, e);
                     }
                     break;
                 case PropertyType.WEAKREFERENCE:
@@ -328,16 +322,26 @@ class BundleWriter {
                     try {
                         writeDate(val.getCalendar());
                     } catch (RepositoryException e) {
-                        // should never occur
-                        throw new IOException("Unexpected error while writing DATE value.");
+                        throw convertToIOException(type, e);
                     }
                     break;
-                default:
+                case PropertyType.STRING:
+                case PropertyType.PATH:
+                case PropertyType.URI:
                     writeString(val.toString());
                     break;
+                default:
+                    throw new IOException("Inknown property type: " + type);
             }
         }
     }
+    
+    private static IOException convertToIOException(int propertyType, Exception e) {
+        String typeName = PropertyType.nameFromValue(propertyType);
+        String message = "Unexpected error for property type "+ typeName +" value.";
+        log.error(message, e);
+        return new IOExceptionWithCause(message, e);
+    }
 
     /**
      * Write a small binary value and return the data.
@@ -368,7 +372,7 @@ class BundleWriter {
             String msg = "Error while storing blob. id="
                     + state.getId() + " idx=" + i + " value=" + value;
             log.error(msg, e);
-            throw new IOException(msg);
+            throw new IOExceptionWithCause(msg, e);
         }
     }
 
@@ -457,6 +461,9 @@ class BundleWriter {
             }
 
             String local = name.getLocalName();
+            if (local.length() == 0) {
+                throw new IOException("Attempt to write an empty local name: " + name);
+            }
             byte[] bytes = local.getBytes("UTF-8");
             int len = Math.min(bytes.length - 1, 0x0f);
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/NodeInfo.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/NodeInfo.java
new file mode 100644
index 0000000..ecc78fe
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/NodeInfo.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.persistence.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.jcr.PropertyType;
+
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.value.InternalValue;
+import org.apache.jackrabbit.spi.Name;
+
+/**
+ * Holds structural information about a node. Used by the consistency checker and garbage collector.
+ */
+public final class NodeInfo {
+
+    /**
+     * The same node id in a NodeInfo graph typically occurs three times: as a the id of the current
+     * NodeInfo, as the parent to another NodeInfo, and as a child of another NodeInfo. In order to
+     * minimize the memory footprint use an NodeId object pool.
+     */
+    private static final ConcurrentMap<NodeId,NodeId> nodeIdPool = new ConcurrentHashMap<NodeId, NodeId>(1000);
+
+    /**
+     * The node id
+     */
+    private final NodeId nodeId;
+
+    /**
+     * The parent node id
+     */
+    private final NodeId parentId;
+
+    /**
+     * The child ids
+     */
+    private List<NodeId> children;
+
+    /**
+     * Map of reference property names of this node with their node id values
+     */
+    private Map<Name, List<NodeId>> references;
+
+    /**
+     * Whether this node is referenceable or not
+     */
+    private boolean isReferenceable;
+
+    /**
+     * Whether this node has blob properties in data storage
+     */
+    private boolean hasBlobsInDataStore;
+
+    /**
+     * Create a new NodeInfo object from a bundle
+     *
+     * @param bundle the node bundle
+     */
+    public NodeInfo(final NodePropBundle bundle) {
+        nodeId = getNodeId(bundle.getId());
+        parentId = getNodeId(bundle.getParentId());
+
+        List<NodePropBundle.ChildNodeEntry> childNodeEntries = bundle.getChildNodeEntries();
+        if (!childNodeEntries.isEmpty()) {
+            children = new ArrayList<NodeId>(childNodeEntries.size());
+            for (NodePropBundle.ChildNodeEntry entry : bundle.getChildNodeEntries()) {
+                children.add(getNodeId(entry.getId()));
+            }
+        } else {
+            children = Collections.emptyList();
+        }
+
+        for (NodePropBundle.PropertyEntry entry : bundle.getPropertyEntries()) {
+            if (entry.getType() == PropertyType.REFERENCE) {
+                if (references == null) {
+                    references = new HashMap<Name, List<NodeId>>(4);
+                }
+                List<NodeId> values = new ArrayList<NodeId>(entry.getValues().length);
+                for (InternalValue value : entry.getValues()) {
+                    values.add(getNodeId(value.getNodeId()));
+                }
+                references.put(entry.getName(), values);
+            }
+            else if (entry.getType() == PropertyType.BINARY) {
+                for (InternalValue internalValue : entry.getValues()) {
+                    if (internalValue.isInDataStore()) {
+                        hasBlobsInDataStore = true;
+                        break;
+                    }
+                }
+
+            }
+        }
+
+        if (references == null) {
+            references = Collections.emptyMap();
+        }
+        isReferenceable = bundle.isReferenceable();
+    }
+
+    /**
+     * @return the node id of this node
+     */
+    public NodeId getId() {
+        return nodeId;
+    }
+
+    /**
+     * @return the parent id of this node
+     */
+    public NodeId getParentId() {
+        return parentId;
+    }
+
+    /**
+     * @return the child ids of this node
+     */
+    public List<NodeId> getChildren() {
+        return children;
+    }
+
+    /**
+     * @return the reference properties along with their node id values of this node
+     */
+    public Map<Name, List<NodeId>> getReferences() {
+        return references;
+    }
+
+    /**
+     * @return whether the node represented by this node info is referenceable
+     */
+    public boolean isReferenceable() {
+        return isReferenceable;
+    }
+
+    /**
+     * @return whether the node has blob properties that are inside the data storage
+     */
+    public boolean hasBlobsInDataStore() {
+        return hasBlobsInDataStore;
+    }
+
+    /**
+     * Simple pool implementation to minimize memory overhead from node id objects
+     * @param nodeId  node id to cache
+     * @return  the cached node id
+     */
+    private static NodeId getNodeId(NodeId nodeId) {
+        if (nodeId == null) {
+            return null;
+        }
+        NodeId cached = nodeIdPool.get(nodeId);
+        if (cached == null) {
+            cached = nodeIdPool.putIfAbsent(nodeId, nodeId);
+            if (cached == null) {
+                cached = nodeId;
+            }
+        }
+        return cached;
+    }
+
+    /**
+     * Clear the NodeId pool.
+     */
+    public static void clearPool() {
+        nodeIdPool.clear();
+    }
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/NodePropBundle.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/NodePropBundle.java
index 1b84566..bdf5e8a 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/NodePropBundle.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/NodePropBundle.java
@@ -434,6 +434,7 @@ public class NodePropBundle {
 
     //--------------------------------------------------------------< Object >
 
+    @Override
     public String toString() {
         StringBuilder builder = new StringBuilder();
         builder.append(id);
@@ -459,20 +460,27 @@ public class NodePropBundle {
         return builder.toString();
     }
 
+    @Override
     public boolean equals(Object object) {
         if (object instanceof NodePropBundle) {
             NodePropBundle that = (NodePropBundle) object;
-            return id.equals(that.id)
-                && parentId.equals(that.parentId)
-                && nodeTypeName.equals(that.nodeTypeName)
-                && mixinTypeNames.equals(that.mixinTypeNames)
+            return equalNullSafe(id, that.id)
+                && equalNullSafe(parentId, that.parentId)
+                && equalNullSafe(nodeTypeName, that.nodeTypeName)
+                && equalNullSafe(mixinTypeNames, that.mixinTypeNames)
                 && isReferenceable == that.isReferenceable
-                && sharedSet.equals(that.sharedSet)
-                && properties.equals(that.properties)
-                && childNodeEntries.equals(that.childNodeEntries);
-        } else {
-            return false;
+                && equalNullSafe(sharedSet, that.sharedSet)
+                && equalNullSafe(properties, that.properties)
+                && equalNullSafe(childNodeEntries, that.childNodeEntries);
         }
+        return false;
+    }
+    
+    private static boolean equalNullSafe(Object a, Object b) {
+        if (a == null || b == null) {
+            return a == b;
+        }
+        return a.equals(b);
     }
 
     //-----------------------------------------------------< ChildNodeEntry >---
@@ -590,6 +598,9 @@ public class NodePropBundle {
             values = state.getValues();
             type = state.getType();
             multiValued = state.isMultiValued();
+            if (!multiValued && values.length != 1) {
+                throw new IllegalArgumentException("Non-multi-valued property with values.length " + values.length);
+            }
             modCount = state.getModCount();
             if (type == PropertyType.BINARY) {
                 blobIds = new String[values.length];
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/OnWorkspaceInconsistency.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/OnWorkspaceInconsistency.java
index b2a9e51..853c6ef 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/OnWorkspaceInconsistency.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/OnWorkspaceInconsistency.java
@@ -161,4 +161,36 @@ public abstract class OnWorkspaceInconsistency {
                                                 NodeState node,
                                                 ChildNodeEntry child)
             throws ItemStateException, RepositoryException;
+
+    /**
+     * Logs a generic workspace inconsistency error.
+     *
+     * @param exception the exception that was thrown when working on the workspace
+     * @param handler   the query handler.
+     * @param path      the path of the parent node.
+     * @param node      the parent node state.
+     * @param child     the child node entry, for which no node state could be
+     *                  found.
+     * @throws RepositoryException if another error occurs not related to item
+     *                             state reading.
+     */
+    public void logError(ItemStateException exception, QueryHandler handler,
+            Path path, NodeState node, ChildNodeEntry child)
+            throws RepositoryException {
+        if (log.isErrorEnabled()) {
+            NamePathResolver resolver = new DefaultNamePathResolver(handler
+                    .getContext().getNamespaceRegistry());
+            StringBuilder err = new StringBuilder();
+            err.append("Workspace inconsistency error on node ");
+            err.append(resolver.getJCRPath(path));
+            err.append(" (");
+            err.append(node.getNodeId());
+            err.append(") with child ");
+            err.append(resolver.getJCRName(child.getName()));
+            err.append(" (");
+            err.append(child.getId());
+            err.append(").");
+            log.error(err.toString(), exception);
+        }
+    }
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryImpl.java
index 13647aa..f65a8b4 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryImpl.java
@@ -36,7 +36,7 @@ import javax.jcr.version.VersionException;
 import org.apache.jackrabbit.api.stats.RepositoryStatistics.Type;
 import org.apache.jackrabbit.core.session.SessionContext;
 import org.apache.jackrabbit.core.session.SessionOperation;
-import org.apache.jackrabbit.core.stats.RepositoryStatisticsImpl;
+import org.apache.jackrabbit.stats.RepositoryStatisticsImpl;
 import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.commons.conversion.NameException;
 import org.slf4j.Logger;
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryObjectModelImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryObjectModelImpl.java
index 33ec2fb..fa27dc7 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryObjectModelImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryObjectModelImpl.java
@@ -37,7 +37,7 @@ import org.apache.jackrabbit.core.query.lucene.SearchIndex;
 import org.apache.jackrabbit.core.query.lucene.join.QueryEngine;
 import org.apache.jackrabbit.core.session.SessionContext;
 import org.apache.jackrabbit.core.session.SessionOperation;
-import org.apache.jackrabbit.core.stats.RepositoryStatisticsImpl;
+import org.apache.jackrabbit.stats.RepositoryStatisticsImpl;
 import org.apache.jackrabbit.spi.commons.query.qom.BindVariableValueImpl;
 import org.apache.jackrabbit.spi.commons.query.qom.DefaultTraversingQOMTreeVisitor;
 import org.apache.jackrabbit.spi.commons.query.qom.QueryObjectModelTree;
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AbstractExcerpt.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AbstractExcerpt.java
index 7d26b0b..7113ea0 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AbstractExcerpt.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AbstractExcerpt.java
@@ -40,6 +40,7 @@ import org.apache.lucene.index.TermPositionVector;
 import org.apache.lucene.index.TermVectorOffsetInfo;
 import org.apache.lucene.search.BooleanClause;
 import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.PhraseQuery;
 import org.apache.lucene.search.Query;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -117,24 +118,6 @@ public abstract class AbstractExcerpt implements HighlightingExcerptProvider {
                 }
                 text.append(separator);
                 text.append(fields[i].stringValue());
-                // this is a hack! in general multiple fields with the same
-                // name are handled properly, that is, offset and position is
-                // calculated correctly. there is one case however where
-                // the offset gets wrong:
-                // if a term text ends with characters that are considered noise
-                // then the offset of the next field will be off by the number
-                // of noise characters.
-                // therefore we delete noise characters at the end of the text.
-                // this process is required for all but the last field
-                if (i < fields.length - 1) {
-                    for (int j = text.length() - 1; j >= 0; j--) {
-                        if (Character.isLetterOrDigit(text.charAt(j))) {
-                            break;
-                        } else {
-                            text.deleteCharAt(j);
-                        }
-                    }
-                }
                 separator = " ";
             }
             TermFreqVector tfv = reader.getTermFreqVector(
@@ -199,7 +182,15 @@ public abstract class AbstractExcerpt implements HighlightingExcerptProvider {
         q.extractTerms(extractedTerms);
         Set<Term> filteredTerms = filterRelevantTerms(extractedTerms);
         if (!filteredTerms.isEmpty()) {
-            relevantTerms.add(filteredTerms.toArray(new Term[] {}));
+            if (q instanceof PhraseQuery) {
+                // inline the terms, basically a 'must all' condition
+                relevantTerms.add(filteredTerms.toArray(new Term[] {}));
+            } else {
+                // each possible term gets a new slot
+                for (Term t : filteredTerms) {
+                    relevantTerms.add(new Term[] { t });
+                }
+            }
         }
     }
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AbstractIndex.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AbstractIndex.java
index a3b1eca..0aa4017 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AbstractIndex.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AbstractIndex.java
@@ -21,7 +21,6 @@ import java.io.OutputStream;
 import java.io.PrintStream;
 import java.io.StringReader;
 import java.util.ArrayList;
-import java.util.BitSet;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
@@ -36,17 +35,21 @@ import org.apache.lucene.document.Fieldable;
 import org.apache.lucene.index.IndexDeletionPolicy;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.LogByteSizeMergePolicy;
+import org.apache.lucene.index.LogMergePolicy;
 import org.apache.lucene.index.Payload;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.Similarity;
 import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.Version;
 import org.apache.tika.io.IOExceptionWithCause;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
  * Implements common functionality for a lucene index.
- * <p/>
+ * <p>
  * Note on synchronization: This class is not entirely thread-safe. Certain
  * concurrent access is however allowed. Read-only access on this index using
  * {@link #getReadOnlyIndexReader()} is thread-safe. That is, multiple threads
@@ -87,9 +90,6 @@ abstract class AbstractIndex {
     /** Compound file flag */
     private boolean useCompoundFile = true;
 
-    /** maxFieldLength config parameter */
-    private int maxFieldLength = SearchIndex.DEFAULT_MAX_FIELD_LENGTH;
-
     /** termInfosIndexDivisor config parameter */
     private int termInfosIndexDivisor = SearchIndex.DEFAULT_TERM_INFOS_INDEX_DIVISOR;
 
@@ -143,8 +143,7 @@ abstract class AbstractIndex {
         this.isExisting = IndexReader.indexExists(directory);
 
         if (!isExisting) {
-            indexWriter = new IndexWriter(directory, analyzer,
-                    IndexWriter.MaxFieldLength.LIMITED);
+            indexWriter = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_36, analyzer));
             // immediately close, now that index has been created
             indexWriter.close();
             indexWriter = null;
@@ -208,14 +207,15 @@ abstract class AbstractIndex {
             });
         }
 
-        try {
-            latch.await();
-        } catch (InterruptedException e) {
-            throw new IOExceptionWithCause(
-                    "Wait for background indexing tasks was interrupted", e);
-        } finally {
-            invalidateSharedReader();
+        for (;;) {
+            try {
+                latch.await();
+                break;
+            } catch (InterruptedException e) {
+                // retry
+            }
         }
+        invalidateSharedReader();
 
         if (!exceptions.isEmpty()) {
             throw new IOExceptionWithCause(
@@ -292,7 +292,7 @@ abstract class AbstractIndex {
                 return readOnlyReader;
             } else {
                 // reader outdated
-                if (readOnlyReader.getRefCount() == 1) {
+                if (readOnlyReader.getRefCountJr() == 1) {
                     // not in use, except by this index
                     // update the reader
                     readOnlyReader.updateDeletedDocs(modifiableReader);
@@ -307,21 +307,15 @@ abstract class AbstractIndex {
             }
         }
         // if we get here there is no up-to-date read-only reader
-        // capture snapshot of deleted documents
-        BitSet deleted = new BitSet(modifiableReader.maxDoc());
-        for (int i = 0; i < modifiableReader.maxDoc(); i++) {
-            if (modifiableReader.isDeleted(i)) {
-                deleted.set(i);
-            }
-        }
         if (sharedReader == null) {
             // create new shared reader
-            IndexReader reader = IndexReader.open(getDirectory(), null, true, termInfosIndexDivisor);
+            IndexReader reader = IndexReader.open(getDirectory(), termInfosIndexDivisor);
             CachingIndexReader cr = new CachingIndexReader(
                     reader, cache, initCache);
             sharedReader = new SharedIndexReader(cr);
         }
-        readOnlyReader = new ReadOnlyIndexReader(sharedReader, deleted, modCount);
+        readOnlyReader = new ReadOnlyIndexReader(sharedReader, 
+                modifiableReader.getDeletedDocs(), modCount);
         readOnlyReader.acquire();
         return readOnlyReader;
     }
@@ -352,10 +346,14 @@ abstract class AbstractIndex {
             indexReader = null;
         }
         if (indexWriter == null) {
-            indexWriter = new IndexWriter(getDirectory(), analyzer,
-                    new IndexWriter.MaxFieldLength(maxFieldLength));
-            indexWriter.setSimilarity(similarity);
-            indexWriter.setUseCompoundFile(useCompoundFile);
+            IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_36, analyzer);
+            config.setSimilarity(similarity);
+            LogMergePolicy mergePolicy = new LogByteSizeMergePolicy();
+            mergePolicy.setUseCompoundFile(useCompoundFile);
+            mergePolicy.setNoCFSRatio(1.0);
+            config.setMergePolicy(mergePolicy);
+
+            indexWriter = new IndexWriter(getDirectory(), config);
             indexWriter.setInfoStream(STREAM_LOGGER);
         }
         return indexWriter;
@@ -388,7 +386,7 @@ abstract class AbstractIndex {
         // optimize if requested
         if (optimize) {
             IndexWriter writer = getIndexWriter();
-            writer.optimize();
+            writer.forceMerge(1, true);
             writer.close();
             indexWriter = null;
         }
@@ -535,23 +533,10 @@ abstract class AbstractIndex {
     //-------------------------< properties >-----------------------------------
 
     /**
-     * The lucene index writer property: useCompountFile
+     * Whether the index writer should use the compound file format
      */
     void setUseCompoundFile(boolean b) {
         useCompoundFile = b;
-        if (indexWriter != null) {
-            indexWriter.setUseCompoundFile(b);
-        }
-    }
-
-    /**
-     * The lucene index writer property: maxFieldLength
-     */
-    void setMaxFieldLength(int maxFieldLength) {
-        this.maxFieldLength = maxFieldLength;
-        if (indexWriter != null) {
-            indexWriter.setMaxFieldLength(maxFieldLength);
-        }
     }
 
     /**
@@ -578,7 +563,7 @@ abstract class AbstractIndex {
      * @param f a lucene field.
      * @return the index parameter on <code>f</code>.
      */
-    private Field.Index getIndexParameter(Fieldable f) {
+    private static Field.Index getIndexParameter(Fieldable f) {
         if (!f.isIndexed()) {
             return Field.Index.NO;
         } else if (f.isTokenized()) {
@@ -594,7 +579,7 @@ abstract class AbstractIndex {
      * @param f a lucene field.
      * @return the term vector parameter on <code>f</code>.
      */
-    private Field.TermVector getTermVectorParameter(Fieldable f) {
+    private static Field.TermVector getTermVectorParameter(Fieldable f) {
         if (f.isStorePositionWithTermVector() && f.isStoreOffsetWithTermVector()) {
             return Field.TermVector.WITH_POSITIONS_OFFSETS;
         } else if (f.isStorePositionWithTermVector()) {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AbstractQueryImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AbstractQueryImpl.java
index da46ba9..9b10f6c 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AbstractQueryImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AbstractQueryImpl.java
@@ -75,7 +75,7 @@ public abstract class AbstractQueryImpl implements ExecutableQuery {
      * the index has stored the nodes. That sequence is stable over multiple
      * invocations of the same query, but will change when nodes get added or
      * removed from the index.
-     * <p/>
+     * <p>
      * The default value for this property is <code>true</code>.
      * @return the current value of this property.
      */
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AbstractWeight.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AbstractWeight.java
index 70792b6..9ddc261 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AbstractWeight.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AbstractWeight.java
@@ -58,7 +58,7 @@ abstract class AbstractWeight extends Weight {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Returns a {@link MultiScorer} if the passed <code>reader</code> is of
      * type {@link MultiIndexReader}.
      */
@@ -77,7 +77,7 @@ abstract class AbstractWeight extends Weight {
             starts[readers.length] = maxDoc;
             Scorer[] scorers = new Scorer[readers.length];
             for (int i = 0; i < readers.length; i++) {
-                scorers[i] = scorer(readers[i], scoreDocsInOrder, topScorer);
+                scorers[i] = scorer(readers[i], scoreDocsInOrder, false);
             }
 
             return new MultiScorer(searcher.getSimilarity(), scorers, starts);
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CachingIndexReader.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CachingIndexReader.java
index 8c0d775..ceade71 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CachingIndexReader.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CachingIndexReader.java
@@ -31,6 +31,7 @@ import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.FieldSelector;
 import org.apache.lucene.index.CorruptIndexException;
+import org.apache.lucene.index.FieldInfos;
 import org.apache.lucene.index.FilterIndexReader;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.Term;
@@ -38,13 +39,14 @@ import org.apache.lucene.index.TermDocs;
 import org.apache.lucene.index.TermEnum;
 import org.apache.lucene.store.IndexInput;
 import org.apache.lucene.store.IndexOutput;
+import org.apache.lucene.util.ReaderUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
  * Implements an <code>IndexReader</code> that maintains caches to resolve
  * {@link #getParent(int, BitSet)} calls efficiently.
- * <p/>
+ * <p>
  */
 class CachingIndexReader extends FilterIndexReader {
 
@@ -251,6 +253,16 @@ class CachingIndexReader extends FilterIndexReader {
 
     //--------------------< FilterIndexReader overwrites >----------------------
 
+    @Override
+    public IndexReader[] getSequentialSubReaders() {
+        return null;
+    }
+
+    @Override
+    public FieldInfos getFieldInfos() {
+        return ReaderUtil.getMergedFieldInfos(in);
+    }
+
     /**
      * Uses the {@link #docNumber2id} cache for document lookups that are only
      * interested in the {@link FieldSelectors#UUID}.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CachingMultiIndexReader.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CachingMultiIndexReader.java
index 8db5169..ea3cf22 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CachingMultiIndexReader.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CachingMultiIndexReader.java
@@ -51,11 +51,6 @@ public final class CachingMultiIndexReader
     private final DocNumberCache cache;
 
     /**
-     * Doc number starts for each sub reader
-     */
-    private int[] starts;
-
-    /**
      * Reference count. Every time close is called refCount is decremented. If
      * refCount drops to zero the underlying readers are closed as well.
      */
@@ -72,15 +67,10 @@ public final class CachingMultiIndexReader
         super(subReaders);
         this.cache = cache;
         this.subReaders = subReaders;
-        starts = new int[subReaders.length + 1];
-        int maxDoc = 0;
         for (int i = 0; i < subReaders.length; i++) {
-            starts[i] = maxDoc;
-            maxDoc += subReaders[i].maxDoc();
             OffsetReader offsetReader = new OffsetReader(subReaders[i], starts[i]);
             readersByCreationTick.put(subReaders[i].getCreationTick(), offsetReader);
         }
-        starts[subReaders.length] = maxDoc;
     }
 
     /**
@@ -213,34 +203,6 @@ public final class CachingMultiIndexReader
         return -1;
     }
 
-    /**
-     * Returns the reader index for document <code>n</code>.
-     * Implementation copied from lucene MultiReader class.
-     *
-     * @param n document number.
-     * @return the reader index.
-     */
-    private int readerIndex(int n) {
-        int lo = 0;                                      // search starts array
-        int hi = subReaders.length - 1;                  // for first element less
-
-        while (hi >= lo) {
-            int mid = (lo + hi) >> 1;
-            int midValue = starts[mid];
-            if (n < midValue) {
-                hi = mid - 1;
-            } else if (n > midValue) {
-                lo = mid + 1;
-            } else {                                      // found a match
-                while (mid + 1 < subReaders.length && starts[mid + 1] == midValue) {
-                    mid++;                                  // scan to last match
-                }
-                return mid;
-            }
-        }
-        return hi;
-    }
-
     //-----------------------< OffsetTermDocs >---------------------------------
 
     /**
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CaseTermQuery.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CaseTermQuery.java
index c530da6..6b21326 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CaseTermQuery.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CaseTermQuery.java
@@ -47,6 +47,7 @@ abstract class CaseTermQuery extends MultiTermQuery implements TransformConstant
     CaseTermQuery(Term term, int transform) {
         this.term = term;
         this.transform = transform;
+        setRewriteMethod(CONSTANT_SCORE_BOOLEAN_QUERY_REWRITE);
     }
 
     /**
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ChildAxisQuery.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ChildAxisQuery.java
index 64aede0..52aff36 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ChildAxisQuery.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ChildAxisQuery.java
@@ -329,9 +329,9 @@ class ChildAxisQuery extends Query implements JackrabbitQuery {
          */
         @Override
         public Scorer scorer(IndexReader reader, boolean scoreDocsInOrder, boolean topScorer) throws IOException {
-            contextScorer = contextQuery.weight(searcher).scorer(reader, scoreDocsInOrder, topScorer);
+            contextScorer = contextQuery.weight(searcher).scorer(reader, scoreDocsInOrder, false);
             if (nameTest != null) {
-                nameTestScorer = new NameQuery(nameTest, version, nsMappings).weight(searcher).scorer(reader, scoreDocsInOrder, topScorer);
+                nameTestScorer = new NameQuery(nameTest, version, nsMappings).weight(searcher).scorer(reader, scoreDocsInOrder, false);
             }
             return new ChildAxisScorer(searcher.getSimilarity(),
                     reader, (HierarchyResolver) reader);
@@ -430,12 +430,13 @@ class ChildAxisQuery extends Query implements JackrabbitQuery {
 
             calculateChildren();
             nextDoc = hits.skipTo(target);
-            while (nextDoc > -1 && !indexIsValid(nextDoc)) {
-                nextDoc();
-            }
             if (nextDoc < 0) {
                 nextDoc = NO_MORE_DOCS;
             }
+
+            while (nextDoc != NO_MORE_DOCS && !indexIsValid(nextDoc)) {
+                nextDoc();
+            }
             return nextDoc;
         }
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ChildNodesQueryHits.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ChildNodesQueryHits.java
index 3d1621a..aeecafd 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ChildNodesQueryHits.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ChildNodesQueryHits.java
@@ -18,6 +18,7 @@ package org.apache.jackrabbit.core.query.lucene;
 
 import org.apache.jackrabbit.core.SessionImpl;
 
+import javax.jcr.ItemNotFoundException;
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 import java.io.IOException;
@@ -99,6 +100,8 @@ public class ChildNodesQueryHits extends AbstractQueryHits {
             try {
                 Node parent = session.getNodeById(nextParent.getNodeId());
                 childHits = new NodeTraversingQueryHits(parent, false, 1);
+            } catch (ItemNotFoundException e) {
+                // access denied to node, will just skip it
             } catch (RepositoryException e) {
                 throw Util.createIOException(e);
             }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CommittableIndexReader.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CommittableIndexReader.java
index affee2d..9275ac1 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CommittableIndexReader.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/CommittableIndexReader.java
@@ -16,11 +16,17 @@
  */
 package org.apache.jackrabbit.core.query.lucene;
 
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.lucene.index.CorruptIndexException;
 import org.apache.lucene.index.FilterIndexReader;
 import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.CorruptIndexException;
-
-import java.io.IOException;
 
 /**
  * Wraps an <code>IndexReader</code> and allows to commit changes without
@@ -29,6 +35,11 @@ import java.io.IOException;
 class CommittableIndexReader extends FilterIndexReader {
 
     /**
+     * The maximum size of the delete history.
+     */
+    private static final int DELETE_HISTORY_SIZE = 1000;
+
+    /**
      * A modification count on this index reader. Initialized with
      * {@link IndexReader#getVersion()} and incremented with every call to
      * {@link #doDelete(int)}.
@@ -36,6 +47,16 @@ class CommittableIndexReader extends FilterIndexReader {
     private volatile long modCount;
 
     /**
+     * The history of the most recent deletes.
+     */
+    private final List<Integer> deleteHistory = new LinkedList<Integer>();
+
+    /**
+    * The deleted docs for this index reader.
+    */
+    private final BitSet deletedDocs = new BitSet();
+
+    /**
      * Creates a new <code>CommittableIndexReader</code> based on <code>in</code>.
      *
      * @param in the <code>IndexReader</code> to wrap.
@@ -43,18 +64,29 @@ class CommittableIndexReader extends FilterIndexReader {
     CommittableIndexReader(IndexReader in) {
         super(in);
         modCount = in.getVersion();
+        int maxDocs = in.maxDoc();
+        for (int i = 0; i < maxDocs; i++) {
+            if (in.isDeleted(i)) {
+                deletedDocs.set(i);
+            }
+        }
     }
 
     //------------------------< FilterIndexReader >-----------------------------
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Increments the modification count.
      */
     protected void doDelete(int n) throws CorruptIndexException, IOException {
         super.doDelete(n);
         modCount++;
+        if (deleteHistory.size() >= DELETE_HISTORY_SIZE) {
+            deleteHistory.remove(0);
+        }
+        deleteHistory.add(n);
+        deletedDocs.set(n);
     }
 
     //------------------------< additional methods >----------------------------
@@ -65,4 +97,54 @@ class CommittableIndexReader extends FilterIndexReader {
     long getModificationCount() {
         return modCount;
     }
+
+    /**
+     * Returns the document numbers of deleted nodes since the given
+     * <code>modCount</code>.
+     *
+     * @param modCount a modification count.
+     * @return document numbers of deleted nodes or <code>null</code> if this
+     *         index reader cannot provide those document number. e.g. modCount
+     *         is too far back in the past.
+     * @throws IllegalArgumentException if <code>modCount</code> is larger than
+     *                                  {@link #getModificationCount()}.
+     */
+    Collection<Integer> getDeletedSince(long modCount)
+            throws IllegalArgumentException {
+        if (modCount > this.modCount) {
+            throw new IllegalArgumentException("modCount: "
+                    + modCount + " > " + this.modCount);
+        }
+        if (modCount == this.modCount) {
+            return Collections.emptyList();
+        }
+        long num = this.modCount - modCount;
+        if (num > deleteHistory.size()) {
+            return null;
+        }
+        List<Integer> deletes = new ArrayList<Integer>((int) num);
+        for (Integer d : deleteHistory.subList((int) (deleteHistory.size() - num),
+                deleteHistory.size())) {
+            deletes.add(d);
+        }
+        return deletes;
+    }
+
+    /**
+     * Returns a copy of the deleted documents BitSet.
+     * @return the deleted documents of this index reader.
+     */
+    BitSet getDeletedDocs() {
+        return (BitSet) deletedDocs.clone();
+    }
+
+    @Override
+    public String toString() {
+      final StringBuilder buffer = new StringBuilder("CommittableIndexReader(");
+      buffer.append(in);
+      buffer.append(',');
+      buffer.append(modCount);
+      buffer.append(')');
+      return buffer.toString();
+    }
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ConsistencyCheck.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ConsistencyCheck.java
index 9ebb76a..8192984 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ConsistencyCheck.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ConsistencyCheck.java
@@ -16,19 +16,38 @@
  */
 package org.apache.jackrabbit.core.query.lucene;
 
+import org.apache.commons.io.IOExceptionWithCause;
+import org.apache.jackrabbit.core.HierarchyManager;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.cluster.ClusterException;
+import org.apache.jackrabbit.core.cluster.ClusterNode;
+import org.apache.jackrabbit.core.persistence.IterablePersistenceManager;
+import org.apache.jackrabbit.core.persistence.PersistenceManager;
+import org.apache.jackrabbit.core.state.ItemState;
 import org.apache.jackrabbit.core.state.ItemStateManager;
+import org.apache.jackrabbit.core.state.NoSuchItemStateException;
 import org.apache.jackrabbit.core.state.NodeState;
 import org.apache.jackrabbit.core.state.ItemStateException;
 import org.apache.jackrabbit.core.state.ChildNodeEntry;
 import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.spi.Path;
+import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.spi.commons.name.PathBuilder;
 import org.apache.lucene.document.Document;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.jcr.ItemNotFoundException;
 import javax.jcr.RepositoryException;
+
 import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.ArrayList;
+import java.util.Map;
 import java.util.Set;
 import java.util.HashSet;
 
@@ -36,12 +55,13 @@ import java.util.HashSet;
  * Implements a consistency check on the search index. Currently the following
  * checks are implemented:
  * <ul>
- * <li>Does not node exist in the ItemStateManager? If it does not exist
+ * <li>Does the node exist in the ItemStateManager? If it does not exist
  * anymore the node is deleted from the index.</li>
  * <li>Is the parent of a node also present in the index? If it is not present it
  * will be indexed.</li>
  * <li>Is a node indexed multiple times? If that is the case, all occurrences
  * in the index for such a node are removed, and the node is re-indexed.</li>
+ * <li>Is a node missing from the index? If so, it is added.</li>
  * </ul>
  */
 public class ConsistencyCheck {
@@ -52,19 +72,41 @@ public class ConsistencyCheck {
     private static final Logger log = LoggerFactory.getLogger(ConsistencyCheck.class);
 
     /**
+     * The number of nodes to fetch at once from the persistence manager. Defaults to 8kb
+     */
+    private static final int NODESATONCE = Integer.getInteger("org.apache.jackrabbit.checker.nodesatonce", 1024 * 8);
+
+    private final SearchIndex handler;
+
+    /**
      * The ItemStateManager of the workspace.
      */
     private final ItemStateManager stateMgr;
 
     /**
+     * The PersistenceManager of the workspace.
+     */
+    private IterablePersistenceManager pm;
+
+    /**
      * The index to check.
      */
     private final MultiIndex index;
 
     /**
-     * All the document ids within the index.
+     * All the node ids and whether they were found in the index.
+     */
+    private Map<NodeId, Boolean> nodeIds;
+
+    /**
+     * Paths of nodes that are not be indexed
      */
-    private Set<NodeId> documentIds;
+    private Set<Path> excludedPaths;
+
+    /**
+     * Paths of nodes that will be excluded from consistency check
+     */
+    private final Set<Path> ignoredPaths = new HashSet<Path>();
 
     /**
      * List of all errors.
@@ -75,21 +117,56 @@ public class ConsistencyCheck {
     /**
      * Private constructor.
      */
-    private ConsistencyCheck(MultiIndex index, ItemStateManager mgr) {
+    private ConsistencyCheck(MultiIndex index, SearchIndex handler, Set<NodeId> excludedIds) {
         this.index = index;
-        this.stateMgr = mgr;
+        this.handler = handler;
+        final HierarchyManager hierarchyManager = handler.getContext().getHierarchyManager();
+        excludedPaths = new HashSet<Path>(excludedIds.size());
+        for (NodeId excludedId : excludedIds) {
+            try {
+                final Path path = hierarchyManager.getPath(excludedId);
+                excludedPaths.add(path);
+            } catch (ItemNotFoundException e) {
+                log.warn("Excluded node does not exist");
+            } catch (RepositoryException e) {
+                log.error("Failed to get excluded path", e);
+            }
+        }
+
+        //JCR-3773: ignore the tree jcr:nodeTypes
+        PathBuilder pathBuilder = new PathBuilder();
+        pathBuilder.addRoot();
+        pathBuilder.addLast(NameConstants.JCR_NODETYPES);
+        try {
+            Path path = pathBuilder.getPath();
+            log.info("consistency check will skip " + path);
+            ignoredPaths.add(path);
+        } catch (MalformedPathException e) {
+            //will never happen
+            log.error("Malformed path", e);
+        }
+
+        this.stateMgr = handler.getContext().getItemStateManager();
+        final PersistenceManager pm = handler.getContext().getPersistenceManager();
+        if (pm instanceof IterablePersistenceManager) {
+            this.pm = (IterablePersistenceManager) pm;
+        }
     }
 
     /**
      * Runs the consistency check on <code>index</code>.
      *
+     *
+     *
      * @param index the index to check.
-     * @param mgr   the ItemStateManager from where to load content.
+     * @param handler the QueryHandler to use.
+     * @param excludedIds the set of node ids that are not indexed
      * @return the consistency check with the results.
      * @throws IOException if an error occurs while checking.
      */
-    static ConsistencyCheck run(MultiIndex index, ItemStateManager mgr) throws IOException {
-        ConsistencyCheck check = new ConsistencyCheck(index, mgr);
+    static ConsistencyCheck run(MultiIndex index, SearchIndex handler, final Set<NodeId> excludedIds)
+            throws IOException {
+        ConsistencyCheck check = new ConsistencyCheck(index, handler, excludedIds);
         check.run();
         return check;
     }
@@ -118,12 +195,11 @@ public class ConsistencyCheck {
                 }
             } catch (Exception e) {
                 if (ignoreFailure) {
-                    log.warn("Exception while reparing: " + e);
-                } else {
-                    if (!(e instanceof IOException)) {
-                        e = new IOException(e.getMessage());
-                    }
+                    log.warn("Exception while repairing: " + error, e);
+                } else if (e instanceof IOException) {
                     throw (IOException) e;
+                } else {
+                    throw new IOExceptionWithCause(e);
                 }
             }
         }
@@ -146,10 +222,80 @@ public class ConsistencyCheck {
      * @throws IOException if an error occurs while running the check.
      */
     private void run() throws IOException {
+        log.info("Checking index of workspace " + handler.getContext().getWorkspace());
+        loadNodes();
+        if (nodeIds != null) {
+            checkIndexConsistency();
+            checkIndexCompleteness();
+        }
+    }
+
+    public void doubleCheckErrors() {
+        if (!errors.isEmpty()) {
+            log.info("Double checking errors");
+            final ClusterNode clusterNode = handler.getContext().getClusterNode();
+            if (clusterNode != null) {
+                try {
+                    clusterNode.sync();
+                } catch (ClusterException e) {
+                    log.error("Could not sync cluster node for double checking errors");
+                }
+            }
+            final Iterator<ConsistencyCheckError> iterator = errors.iterator();
+            while (iterator.hasNext()) {
+                try {
+                    final ConsistencyCheckError error = iterator.next();
+                    if (!error.doubleCheck(handler, stateMgr)) {
+                        log.info("False positive: " + error.toString());
+                        iterator.remove();
+                    }
+                } catch (RepositoryException e) {
+                    log.error("Failed to double check consistency error", e);
+                } catch (IOException e) {
+                    log.error("Failed to double check consistency error", e);
+                }
+            }
+        }
+    }
+
+    private void loadNodes() {
+        log.info("Loading nodes");
+        try {
+            int count = 0;
+            Map<NodeId, Boolean> nodeIds = new HashMap<NodeId, Boolean>();
+            List<NodeId> batch = pm.getAllNodeIds(null, NODESATONCE);
+            NodeId lastId = null;
+            while (!batch.isEmpty()) {
+                for (NodeId nodeId : batch) {
+                    lastId = nodeId;
+
+                    count++;
+                    if (count % 1000 == 0) {
+                        log.info(pm + ": loaded " + count + " node ids...");
+                    }
+
+                    nodeIds.put(nodeId, Boolean.FALSE);
+
+                }
+                batch = pm.getAllNodeIds(lastId, NODESATONCE);
+            }
+            if (pm.exists(lastId)) {
+                this.nodeIds = nodeIds;
+            } else {
+                log.info("Failed to read all nodes, starting over");
+                loadNodes();
+            }
+        } catch (ItemStateException e) {
+            log.error("Exception while loading items to check", e);
+        } catch (RepositoryException e) {
+            log.error("Exception while loading items to check", e);
+        }
+    }
+
+    private void checkIndexConsistency() throws IOException {
+        log.info("Checking index consistency");
         // Ids of multiple nodes in the index
         Set<NodeId> multipleEntries = new HashSet<NodeId>();
-        // collect all documents ids
-        documentIds = new HashSet<NodeId>();
         CachingMultiIndexReader reader = index.getIndexReader();
         try {
             for (int i = 0; i < reader.maxDoc(); i++) {
@@ -162,12 +308,16 @@ public class ConsistencyCheck {
                 }
                 Document d = reader.document(i, FieldSelectors.UUID);
                 NodeId id = new NodeId(d.get(FieldNames.UUID));
-                if (stateMgr.hasItemState(id)) {
-                    if (!documentIds.add(id)) {
-                        multipleEntries.add(id);
+                if (!isIgnored(id)) {
+                    boolean nodeExists = nodeIds.containsKey(id);
+                    if (nodeExists) {
+                        Boolean alreadyIndexed = nodeIds.put(id, Boolean.TRUE);
+                        if (alreadyIndexed) {
+                            multipleEntries.add(id);
+                        }
+                    } else {
+                        errors.add(new NodeDeleted(id));
                     }
-                } else {
-                    errors.add(new NodeDeleted(id));
                 }
             }
         } finally {
@@ -192,24 +342,122 @@ public class ConsistencyCheck {
                 }
                 Document d = reader.document(i, FieldSelectors.UUID_AND_PARENT);
                 NodeId id = new NodeId(d.get(FieldNames.UUID));
-                String parentUUIDString = d.get(FieldNames.PARENT);
-                NodeId parentId = null;
-                if (parentUUIDString.length() > 0) {
-                    parentId = new NodeId(parentUUIDString);
+                if (!nodeIds.containsKey(id) || isIgnored(id)) {
+                    // this node is ignored or was already marked for deletion
+                    continue;
                 }
-                if (parentId == null || documentIds.contains(parentId)) {
+                String parent = d.get(FieldNames.PARENT);
+                if (parent == null || parent.isEmpty()) {
                     continue;
                 }
-                // parent is missing
-                if (stateMgr.hasItemState(parentId)) {
+                final NodeId parentId = new NodeId(parent);
+
+                boolean parentExists = nodeIds.containsKey(parentId);
+                boolean parentIndexed = parentExists && nodeIds.get(parentId);
+                if (parentIndexed) {
+                    continue;
+                } else if (id.equals(RepositoryImpl.SYSTEM_ROOT_NODE_ID)
+                        && parentId.equals(RepositoryImpl.ROOT_NODE_ID)) {
+                    continue; // special case for the /jcr:system node
+                }
+
+                // parent is missing from index
+                if (parentExists) {
                     errors.add(new MissingAncestor(id, parentId));
                 } else {
-                    errors.add(new UnknownParent(id, parentId));
+                    try {
+                        final ItemState itemState = stateMgr.getItemState(id);
+                        if (parentId.equals(itemState.getParentId())) {
+                            // orphaned node
+                            errors.add(new UnknownParent(id, parentId));
+                        } else {
+                            errors.add(new WrongParent(id, parentId, itemState.getParentId()));
+                        }
+                    } catch (ItemStateException ignored) {
+                    }
                 }
             }
         } finally {
             reader.release();
         }
+
+    }
+
+    private void checkIndexCompleteness() {
+        log.info("Checking index completeness");
+        int i = 0;
+        int size = nodeIds.size();
+        for (Map.Entry<NodeId, Boolean> entry : nodeIds.entrySet()) {
+            // check whether all nodes in the repository are indexed
+            NodeId nodeId = entry.getKey();
+            boolean indexed = entry.getValue();
+            try {
+                if (++i > 10 && i % (size / 10) == 0) {
+                    long progress = Math.round((100.0 * (float) i) / (float) size);
+                    log.info("progress: " + progress + "%");
+                }
+                if (!indexed && !isIgnored(nodeId) && !isExcluded(nodeId)) {
+                    NodeState nodeState = getNodeState(nodeId);
+                    if (nodeState != null && !isBrokenNode(nodeId, nodeState)) {
+                        errors.add(new NodeAdded(nodeId));
+                    }
+                }
+            } catch (ItemStateException e) {
+                log.error("Failed to check node: " + nodeId, e);
+            }
+        }
+    }
+
+    private boolean isExcluded(NodeId id) {
+        try {
+            final HierarchyManager hierarchyManager = handler.getContext().getHierarchyManager();
+            final Path path = hierarchyManager.getPath(id);
+            for (Path excludedPath : excludedPaths) {
+                if (excludedPath.isEquivalentTo(path) || excludedPath.isAncestorOf(path)) {
+                    return true;
+                }
+            }
+        } catch (RepositoryException ignored) {
+        }
+        return false;
+    }
+
+    private boolean isIgnored(NodeId id) {
+        try {
+            final HierarchyManager hierarchyManager = handler.getContext().getHierarchyManager();
+            final Path path = hierarchyManager.getPath(id);
+            for (Path excludedPath : ignoredPaths) {
+                if (excludedPath.isEquivalentTo(path) || excludedPath.isAncestorOf(path)) {
+                    return true;
+                }
+            }
+        } catch (RepositoryException ignored) {
+        }
+        return false;
+    }
+
+    private NodeState getNodeState(NodeId nodeId) throws ItemStateException {
+        try {
+            return (NodeState) stateMgr.getItemState(nodeId);
+        } catch (NoSuchItemStateException e) {
+            return null;
+        }
+    }
+
+    private boolean isBrokenNode(final NodeId nodeId, final NodeState nodeState) throws ItemStateException {
+        final NodeId parentId = nodeState.getParentId();
+        if (parentId != null) {
+            final NodeState parentState = getNodeState(parentId);
+            if (parentState == null) {
+                log.warn("Node missing from index is orphaned node: " + nodeId);
+                return true;
+            }
+            if (!parentState.hasChildNodeEntry(nodeId)) {
+                log.warn("Node missing from index is abandoned node: " + nodeId);
+                return true;
+            }
+        }
+        return false;
     }
 
     /**
@@ -222,13 +470,18 @@ public class ConsistencyCheck {
     private String getPath(NodeState node) {
         // remember as fallback
         String uuid = node.getNodeId().toString();
-        StringBuffer path = new StringBuffer();
+        StringBuilder path = new StringBuilder();
         List<ChildNodeEntry> elements = new ArrayList<ChildNodeEntry>();
         try {
             while (node.getParentId() != null) {
                 NodeId parentId = node.getParentId();
                 NodeState parent = (NodeState) stateMgr.getItemState(parentId);
                 ChildNodeEntry entry = parent.getChildNodeEntry(node.getNodeId());
+                if (entry == null) {
+                    log.warn("Failed to build path: abandoned child {} of node {}. " +
+                            "Please run a repository consistency check", node.getNodeId(), parentId);
+                    return uuid;
+                }
                 elements.add(entry);
                 node = parent;
             }
@@ -272,34 +525,48 @@ public class ConsistencyCheck {
 
         /**
          * Repairs the missing node by indexing the missing ancestors.
-         * @throws IOException if an error occurs while repairing.
+         * @throws Exception if an error occurs while repairing.
          */
-        public void repair() throws IOException {
+        public void repair() throws Exception {
             NodeId ancestorId = parentId;
-            while (ancestorId != null && !documentIds.contains(ancestorId)) {
-                try {
-                    NodeState n = (NodeState) stateMgr.getItemState(ancestorId);
-                    log.info("Reparing missing node " + getPath(n));
-                    Document d = index.createDocument(n);
-                    index.addDocument(d);
-                    documentIds.add(n.getNodeId());
-                    ancestorId = n.getParentId();
-                } catch (ItemStateException e) {
-                    throw new IOException(e.toString());
-                } catch (RepositoryException e) {
-                    throw new IOException(e.toString());
+            while (ancestorId != null && nodeIds.containsKey(ancestorId) && nodeIds.get(ancestorId)) {
+                NodeState n = (NodeState) stateMgr.getItemState(ancestorId);
+                log.info("Repairing missing node " + getPath(n) + " (" + ancestorId + ")");
+                Document d = index.createDocument(n);
+                index.addDocument(d);
+                nodeIds.put(n.getNodeId(), Boolean.TRUE);
+                ancestorId = n.getParentId();
+            }
+        }
+
+        @Override
+        boolean doubleCheck(SearchIndex handler, ItemStateManager stateManager)
+                throws RepositoryException, IOException {
+            final List<Document> documents = handler.getNodeDocuments(id);
+            for (Document document : documents) {
+                final String parent = document.get(FieldNames.PARENT);
+                if (parent != null && !parent.isEmpty()) {
+                    final NodeId parentId = new NodeId(parent);
+                    if (handler.getNodeDocuments(parentId).isEmpty()) {
+                        return true;
+                    }
                 }
             }
+            return false;
+
         }
     }
 
     /**
-     * The parent of a node is not available through the ItemStateManager.
+     * The parent of a node is not in the repository
      */
     private static class UnknownParent extends ConsistencyCheckError {
 
+        private NodeId parentId;
+
         private UnknownParent(NodeId id, NodeId parentId) {
             super("Node " + id + " has unknown parent: " + parentId, id);
+            this.parentId = parentId;
         }
 
         /**
@@ -313,9 +580,77 @@ public class ConsistencyCheck {
         /**
          * No operation.
          */
-        public void repair() throws IOException {
+        public void repair() {
             log.warn("Unknown parent for " + id + " cannot be repaired");
         }
+
+        @Override
+        boolean doubleCheck(SearchIndex handler, ItemStateManager stateManager)
+                throws IOException, RepositoryException {
+            final List<Document> documents = handler.getNodeDocuments(id);
+            for (Document document : documents) {
+                final String parent = document.get(FieldNames.PARENT);
+                if (parent != null && !parent.isEmpty()) {
+                    final NodeId parentId = new NodeId(parent);
+                    if (parentId.equals(this.parentId) && !stateManager.hasItemState(parentId)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * The parent as indexed does not correspond with the actual parent in the repository
+     */
+    private class WrongParent extends ConsistencyCheckError {
+
+        private NodeId indexedParentId;
+
+        private WrongParent(NodeId id, NodeId indexedParentId, NodeId actualParentId) {
+            super("Node " + id + " has wrong parent: " + indexedParentId + ", should be : " + actualParentId, id);
+            this.indexedParentId = indexedParentId;
+        }
+
+        @Override
+        public boolean repairable() {
+            return true;
+        }
+
+        /**
+         * Reindex node.
+         */
+        @Override
+        void repair() throws Exception {
+            index.removeAllDocuments(id);
+            try {
+                NodeState node = (NodeState) stateMgr.getItemState(id);
+                log.info("Re-indexing node with wrong parent in index: " + getPath(node));
+                Document d = index.createDocument(node);
+                index.addDocument(d);
+                nodeIds.put(node.getNodeId(), Boolean.TRUE);
+            } catch (NoSuchItemStateException e) {
+                log.info("Not re-indexing node with wrong parent because node no longer exists");
+            }
+        }
+
+        @Override
+        boolean doubleCheck(final SearchIndex handler, final ItemStateManager stateManager)
+                throws RepositoryException, IOException {
+            final List<Document> documents = handler.getNodeDocuments(id);
+            for (Document document : documents) {
+                final String parent = document.get(FieldNames.PARENT);
+                if (parent != null && !parent.isEmpty()) {
+                    final NodeId parentId = new NodeId(parent);
+                    if (parentId.equals(indexedParentId) && !stateManager.hasItemState(parentId)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+
     }
 
     /**
@@ -340,7 +675,7 @@ public class ConsistencyCheck {
          * re-index the node.
          * @throws IOException if an error occurs while repairing.
          */
-        public void repair() throws IOException {
+        public void repair() throws Exception {
             // first remove all occurrences
             index.removeAllDocuments(id);
             // then re-index the node
@@ -349,13 +684,17 @@ public class ConsistencyCheck {
                 log.info("Re-indexing duplicate node occurrences in index: " + getPath(node));
                 Document d = index.createDocument(node);
                 index.addDocument(d);
-                documentIds.add(node.getNodeId());
-            } catch (ItemStateException e) {
-                throw new IOException(e.toString());
-            } catch (RepositoryException e) {
-                throw new IOException(e.toString());
+                nodeIds.put(node.getNodeId(), Boolean.TRUE);
+            } catch (NoSuchItemStateException e) {
+                log.info("Not re-indexing node with multiple occurrences because node no longer exists");
             }
         }
+
+        @Override
+        boolean doubleCheck(SearchIndex handler, ItemStateManager stateManager)
+                throws RepositoryException, IOException {
+            return handler.getNodeDocuments(id).size() > 1;
+        }
     }
 
     /**
@@ -364,7 +703,7 @@ public class ConsistencyCheck {
     private class NodeDeleted extends ConsistencyCheckError {
 
         NodeDeleted(NodeId id) {
-            super("Node " + id + " does not longer exist.", id);
+            super("Node " + id + " no longer exists.", id);
         }
 
         /**
@@ -383,5 +722,55 @@ public class ConsistencyCheck {
             log.info("Removing deleted node from index: " + id);
             index.removeDocument(id);
         }
+
+        @Override
+        boolean doubleCheck(SearchIndex handler, ItemStateManager stateManager)
+                throws RepositoryException, IOException {
+            final List<Document> documents = handler.getNodeDocuments(id);
+            if (!documents.isEmpty()) {
+                if (!stateManager.hasItemState(id)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    private class NodeAdded extends ConsistencyCheckError {
+
+        NodeAdded(final NodeId id) {
+            super("Node " + id + " is missing.", id);
+        }
+
+        @Override
+        public boolean repairable() {
+            return true;
+        }
+
+        @Override
+        void repair() throws Exception {
+            try {
+                NodeState nodeState = (NodeState) stateMgr.getItemState(id);
+                log.info("Adding missing node to index: " + getPath(nodeState));
+                final Iterator<NodeId> remove = Collections.<NodeId>emptyList().iterator();
+                final Iterator<NodeState> add = Collections.singletonList(nodeState).iterator();
+                handler.updateNodes(remove, add);
+            } catch (NoSuchItemStateException e) {
+                log.info("Not adding missing node because node no longer exists");
+            }
+        }
+
+        @Override
+        boolean doubleCheck(SearchIndex handler, ItemStateManager stateManager)
+                throws RepositoryException, IOException {
+            final List<Document> documents = handler.getNodeDocuments(id);
+            if (documents.isEmpty()) {
+                if (stateManager.hasItemState(id)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
     }
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ConsistencyCheckError.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ConsistencyCheckError.java
index dc3911e..5281548 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ConsistencyCheckError.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ConsistencyCheckError.java
@@ -17,9 +17,12 @@
 package org.apache.jackrabbit.core.query.lucene;
 
 import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.state.ItemStateManager;
 
 import java.io.IOException;
 
+import javax.jcr.RepositoryException;
+
 /**
  * Common base class for errors detected during the consistency check.
  */
@@ -56,7 +59,17 @@ public abstract class ConsistencyCheckError {
 
     /**
      * Executes the repair operation.
-     * @throws IOException if an error occurs while repairing.
+     * @throws Exception if an error occurs while repairing.
+     */
+    abstract void repair() throws Exception;
+
+    /**
+     * Double check the error. Used to rule out false positives in live environments.
+     *
+     * @return <code>true</code> if the error was verified to still exist, else <code>false</code>.
+     * @throws RepositoryException
+     * @throws IOException
      */
-    abstract void repair() throws IOException;
+    abstract boolean doubleCheck(SearchIndex handler, ItemStateManager stateManager)
+            throws RepositoryException, IOException;
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DefaultHighlighter.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DefaultHighlighter.java
index 6628880..145ee1a 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DefaultHighlighter.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DefaultHighlighter.java
@@ -36,7 +36,7 @@ import org.apache.lucene.index.TermVectorOffsetInfo;
 /**
  * This is an adapted version of the <code>FulltextHighlighter</code> posted in
  * issue: <a href="http://issues.apache.org/jira/browse/LUCENE-644">LUCENE-644</a>.
- * <p/>
+ * <p>
  * Important: for this highlighter to function properly, field must be stored
  * with token offsets.<br/> Use Field constructor {@link
  * Field#Field(String,String,Field.Store,Field.Index,Field.TermVector)
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DefaultRedoLog.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DefaultRedoLog.java
index fc0f245..efbcb70 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DefaultRedoLog.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DefaultRedoLog.java
@@ -39,7 +39,7 @@ import java.util.List;
  * redo log is written to keep track of the changes. In case the Jackrabbit
  * process terminates unexpected the redo log is applied when Jackrabbit is
  * restarted the next time.
- * <p/>
+ * <p>
  * This class is not thread-safe.
  */
 class DefaultRedoLog implements RedoLog {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DerefQuery.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DerefQuery.java
index 89f2210..86d3c86 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DerefQuery.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DerefQuery.java
@@ -205,9 +205,9 @@ class DerefQuery extends Query {
         @Override
         public Scorer scorer(IndexReader reader, boolean scoreDocsInOrder,
                 boolean topScorer) throws IOException {
-            contextScorer = contextQuery.weight(searcher).scorer(reader, scoreDocsInOrder, topScorer);
+            contextScorer = contextQuery.weight(searcher).scorer(reader, scoreDocsInOrder, false);
             if (nameTest != null) {
-                nameTestScorer = new NameQuery(nameTest, version, nsMappings).weight(searcher).scorer(reader, scoreDocsInOrder, topScorer);
+                nameTestScorer = new NameQuery(nameTest, version, nsMappings).weight(searcher).scorer(reader, scoreDocsInOrder, false);
             }
             return new DerefScorer(searcher.getSimilarity(), reader);
         }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DescendantSelfAxisQuery.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DescendantSelfAxisQuery.java
index 9f805cb..1318b93 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DescendantSelfAxisQuery.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/DescendantSelfAxisQuery.java
@@ -392,8 +392,8 @@ class DescendantSelfAxisQuery extends Query implements JackrabbitQuery {
          */
         public Scorer scorer(IndexReader reader, boolean scoreDocsInOrder,
                 boolean topScorer) throws IOException {
-            contextScorer = contextQuery.weight(searcher).scorer(reader, scoreDocsInOrder, topScorer);
-            subScorer = subQuery.weight(searcher).scorer(reader, scoreDocsInOrder, topScorer);
+            contextScorer = searcher.createNormalizedWeight(contextQuery).scorer(reader, scoreDocsInOrder, false);
+            subScorer = searcher.createNormalizedWeight(subQuery).scorer(reader, scoreDocsInOrder, false);
             HierarchyResolver resolver = (HierarchyResolver) reader;
             return new DescendantSelfAxisScorer(searcher.getSimilarity(), reader, resolver);
         }
@@ -471,9 +471,14 @@ class DescendantSelfAxisQuery extends Query implements JackrabbitQuery {
             }
 
             collectContextHits();
-            currentDoc = subScorer.nextDoc();
             if (contextHits.isEmpty()) {
                 currentDoc = NO_MORE_DOCS;
+            } else {
+                if (subScorer != null) {
+                    currentDoc = subScorer.nextDoc();
+                } else {
+                    currentDoc = NO_MORE_DOCS;
+                }
             }
             while (currentDoc != NO_MORE_DOCS) {
                 if (isValid(currentDoc)) {
@@ -505,12 +510,14 @@ class DescendantSelfAxisQuery extends Query implements JackrabbitQuery {
             // optimize in the case of an advance to finish.
             // see https://issues.apache.org/jira/browse/JCR-3082
             if (target == NO_MORE_DOCS) {
-                subScorer.advance(target);
+                if (subScorer != null) {
+                    subScorer.advance(target);
+                }
                 currentDoc = NO_MORE_DOCS;
                 return currentDoc;
             }
 
-            currentDoc = subScorer.nextDoc();
+            currentDoc = subScorer.advance(target);
             if (currentDoc == NO_MORE_DOCS) {
                 return NO_MORE_DOCS;
             } else {
@@ -522,12 +529,14 @@ class DescendantSelfAxisQuery extends Query implements JackrabbitQuery {
         private void collectContextHits() throws IOException {
             if (!contextHitsCalculated) {
                 long time = System.currentTimeMillis();
-                contextScorer.score(new AbstractHitCollector() {
-                    @Override
-                    protected void collect(int doc, float score) {
-                        contextHits.set(doc);
-                    }
-                }); // find all
+                if (contextScorer != null) {
+                    contextScorer.score(new AbstractHitCollector() {
+                        @Override
+                        protected void collect(int doc, float score) {
+                            contextHits.set(doc);
+                        }
+                    }); // find all
+                }
                 contextHitsCalculated = true;
                 time = System.currentTimeMillis() - time;
                 if (log.isDebugEnabled()) {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FileBasedNamespaceMappings.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FileBasedNamespaceMappings.java
index 3265bd5..67c18a0 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FileBasedNamespaceMappings.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FileBasedNamespaceMappings.java
@@ -37,7 +37,7 @@ import java.util.Properties;
  * {@link NamespaceResolver} that holds a namespace
  * mapping that is used internally in the search index. Storing paths with the
  * full uri of a namespace would require too much space in the search index.
- * <p/>
+ * <p>
  * Whenever a yet unknown namespace uri to prefix mapping is requested, a new
  * prefix is created on the fly and associated with the namespace. Known
  * namespace mappings are stored in a properties file.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FilterSearcher.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FilterSearcher.java
new file mode 100644
index 0000000..322e5c7
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FilterSearcher.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.query.lucene;
+
+import java.io.IOException;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.FieldSelector;
+import org.apache.lucene.index.CorruptIndexException;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.Collector;
+import org.apache.lucene.search.Explanation;
+import org.apache.lucene.search.Filter;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Searcher;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.TopFieldDocs;
+import org.apache.lucene.search.Weight;
+
+/**
+ * <code>FilterSearcher</code> wraps another Searcher and forwards all
+ * calls to the wrapped Searcher.
+ */
+class FilterSearcher extends Searcher {
+
+    private Searcher s;
+
+    FilterSearcher(Searcher searcher) {
+        this.s = searcher;
+    }
+
+    @Override
+    public void search(Weight weight, Filter filter, Collector results)
+            throws IOException {
+        s.search(weight, filter, results);
+    }
+
+    @Override
+    public void close() throws IOException {
+        s.close();
+    }
+
+    @Override
+    public int docFreq(Term term) throws IOException {
+        return s.docFreq(term);
+    }
+
+    @Override
+    public int maxDoc() throws IOException {
+        return s.maxDoc();
+    }
+
+    @Override
+    public TopDocs search(Weight weight, Filter filter, int n)
+            throws IOException {
+        return s.search(weight, filter, n);
+    }
+
+    @Override
+    public Document doc(int i) throws CorruptIndexException, IOException {
+        return s.doc(i);
+    }
+
+    @Override
+    public Document doc(int docid, FieldSelector fieldSelector)
+            throws CorruptIndexException, IOException {
+        return s.doc(docid, fieldSelector);
+    }
+
+    @Override
+    public Query rewrite(Query query) throws IOException {
+        return s.rewrite(query);
+    }
+
+    @Override
+    public Explanation explain(Weight weight, int doc) throws IOException {
+        return s.explain(weight, doc);
+    }
+
+    @Override
+    public TopFieldDocs search(Weight weight, Filter filter, int n, Sort sort)
+            throws IOException {
+        return null;
+    }
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IDField.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IDField.java
index 9105980..77d23ca 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IDField.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IDField.java
@@ -21,6 +21,8 @@ import java.io.Reader;
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.lucene.analysis.TokenStream;
 import org.apache.lucene.document.AbstractField;
+import org.apache.lucene.document.Field.TermVector;
+import org.apache.lucene.index.FieldInfo.IndexOptions;
 
 /**
  * <code>IDField</code> implements a lucene field for the id of a node.
@@ -37,7 +39,8 @@ public class IDField extends AbstractField {
         this.isStored = true;
         this.isTokenized = false;
         this.omitNorms = true;
-        this.omitTermFreqAndPositions = true;
+        setIndexOptions(IndexOptions.DOCS_ONLY);
+        setStoreTermVector(TermVector.NO);
     }
 
     public String stringValue() {
@@ -48,10 +51,6 @@ public class IDField extends AbstractField {
         return null;
     }
 
-    public byte[] binaryValue() {
-        return null;
-    }
-
     public TokenStream tokenStreamValue() {
         return null;
     }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IOCounters.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IOCounters.java
index 061e92c..423aaa1 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IOCounters.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IOCounters.java
@@ -19,12 +19,17 @@ package org.apache.jackrabbit.core.query.lucene;
 import java.util.Map;
 import java.util.WeakHashMap;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 /**
  * <code>IOCounters</code> provides a basic mechanism to track I/O during query
  * execution.
  */
 public class IOCounters {
 
+    private static final Logger log = LoggerFactory.getLogger(IOCounters.class);
+
     private static final Map<Thread, Long> counts =
         new WeakHashMap<Thread, Long>();
 
@@ -39,7 +44,11 @@ public class IOCounters {
     /**
      * Increments the read count caused by the current thread.
      */
-    public static synchronized void incrRead() {
-        counts.put(Thread.currentThread(), getReads() + 1);
+    public static void incrRead() {
+        if (log.isDebugEnabled()) {
+            synchronized (IOCounters.class) {
+                counts.put(Thread.currentThread(), getReads() + 1);
+            }
+        }
     }
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexFormatVersion.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexFormatVersion.java
index 82017fa..48be36a 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexFormatVersion.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexFormatVersion.java
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.core.query.lucene;
 import java.util.Collection;
 
 import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.util.ReaderUtil;
 
 /**
  * This class indicates the lucene index format that is used.
@@ -102,8 +103,7 @@ public class IndexFormatVersion {
      * index reader.
      */
     public static IndexFormatVersion getVersion(IndexReader indexReader) {
-        Collection<String> fields = indexReader.getFieldNames(
-                IndexReader.FieldOption.ALL);
+        Collection<String> fields = ReaderUtil.getIndexedFields(indexReader);
         if (fields.contains(FieldNames.LOCAL_NAME) || indexReader.numDocs() == 0) {
             return IndexFormatVersion.V3;
         } else if (fields.contains(FieldNames.PROPERTIES_SET)) {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexHistory.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexHistory.java
index 1f78044..6f327e1 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexHistory.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexHistory.java
@@ -84,8 +84,12 @@ class IndexHistory {
                     } else {
                         continue;
                     }
-                    IndexInfos infos = new IndexInfos(dir, INDEXES, gen);
-                    indexInfosMap.put(gen, infos);
+                    try {
+                        IndexInfos infos = new IndexInfos(dir, INDEXES, gen);
+                        indexInfosMap.put(gen, infos);
+                    } catch (IOException e) {
+                        log.warn("ignoring invalid index infos file: " + name);
+                    }
                 }
             }
         }
@@ -97,7 +101,7 @@ class IndexHistory {
      * say until when an index segment was in use, but it does guarantee that
      * the index segment in question was not in use anymore at the returned
      * time.
-     * <p/>
+     * <p>
      * There are two special cases of return values:
      * <ul>
      * <li>{@link Long#MAX_VALUE}: indicates that the index segment is still in active use.</li>
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexInfos.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexInfos.java
index 693d693..9b752a1 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexInfos.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexInfos.java
@@ -19,11 +19,14 @@ package org.apache.jackrabbit.core.query.lucene;
 import java.io.BufferedInputStream;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
+import java.io.EOFException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.BufferedOutputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -98,13 +101,25 @@ class IndexInfos implements Cloneable {
     IndexInfos(Directory dir, String baseName) throws IOException {
         this.directory = dir;
         this.name = baseName;
-        long gen = getCurrentGeneration(getFileNames(dir, baseName), baseName);
-        if (gen == -1) {
+        long gens[] = getGenerations(getFileNames(dir, baseName), baseName);
+        if (gens.length == 0) {
             // write initial infos
             write();
         } else {
-            this.generation = gen;
-            read();
+            // read most recent generation
+            for (int i = gens.length - 1; i >= 0; i--) {
+                try {
+                    this.generation = gens[i];
+                    read();
+                    break;
+                } catch (EOFException e) {
+                    String fileName = getFileName(gens[i]);
+                    log.warn("deleting invalid index infos file: " + fileName);
+                    dir.deleteFile(fileName);
+                    // reset generation
+                    this.generation = 0;
+                }
+            }
         }
     }
 
@@ -166,6 +181,7 @@ class IndexInfos implements Cloneable {
             } finally {
                 out.close();
             }
+            directory.sync(Collections.singleton(newName));
             lastModified = System.currentTimeMillis();
             success = true;
         } finally {
@@ -378,22 +394,18 @@ class IndexInfos implements Cloneable {
     }
 
     /**
-     * Returns the most current generation of the given files.
+     * Returns the generations fo the given files in ascending order.
      *
-     * @param fileNames the file names from where to obtain the generation.
+     * @param fileNames the file names from where to obtain the generations.
      * @param base the base name.
-     * @return the most current generation.
+     * @return the generations in ascending order.
      */
-    private static long getCurrentGeneration(String[] fileNames, String base) {
-        long max = -1;
-        int i = 0;
-        while (i < fileNames.length) {
-            long gen = generationFromFileName(fileNames[i], base);
-            if (gen > max) {
-                max = gen;
-            }
-            i++;
+    private static long[] getGenerations(String[] fileNames, String base) {
+        long[] gens = new long[fileNames.length];
+        for (int i = 0; i < fileNames.length; i++) {
+            gens[i] = generationFromFileName(fileNames[i], base);
         }
-        return max;
+        Arrays.sort(gens);
+        return gens;
     }
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexMigration.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexMigration.java
index cec8702..dbdc7d2 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexMigration.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexMigration.java
@@ -33,13 +33,20 @@ import org.apache.lucene.document.Field;
 import org.apache.lucene.document.FieldSelector;
 import org.apache.lucene.document.Fieldable;
 import org.apache.lucene.index.CorruptIndexException;
+import org.apache.lucene.index.FieldInfos;
 import org.apache.lucene.index.FilterIndexReader;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.index.KeepOnlyLastCommitDeletionPolicy;
+import org.apache.lucene.index.LogByteSizeMergePolicy;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.TermEnum;
 import org.apache.lucene.index.TermPositions;
+import org.apache.lucene.index.UpgradeIndexMergePolicy;
 import org.apache.lucene.store.Directory;
+import org.apache.lucene.util.ReaderUtil;
+import org.apache.lucene.util.Version;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -102,21 +109,23 @@ public class IndexMigration {
         // if we get here then the index must be migrated
         log.debug("Index requires migration {}", indexDir);
 
-        String migrationName = index.getName() + "_v2.3";
+        String migrationName = index.getName() + "_v36";
         if (directoryManager.hasDirectory(migrationName)) {
             directoryManager.delete(migrationName);
         }
 
         Directory migrationDir = directoryManager.getDirectory(migrationName);
+        final IndexWriterConfig c = new IndexWriterConfig(Version.LUCENE_36, new JackrabbitAnalyzer());
+        c.setMergePolicy(new UpgradeIndexMergePolicy(new LogByteSizeMergePolicy()));
+        c.setIndexDeletionPolicy(new KeepOnlyLastCommitDeletionPolicy()); 
         try {
-            IndexWriter writer = new IndexWriter(migrationDir, new JackrabbitAnalyzer(),
-                    IndexWriter.MaxFieldLength.UNLIMITED);
+            IndexWriter writer = new IndexWriter(migrationDir, c);
             try {
-                IndexReader r = new MigrationIndexReader(
-                        IndexReader.open(index.getDirectory(), true),
+                IndexReader r = new MigrationIndexReader(IndexReader.open(index.getDirectory()),
                         oldSeparatorChar);
                 try {
-                    writer.addIndexes(new IndexReader[]{r});
+                    writer.addIndexes(r);
+                    writer.forceMerge(1);
                     writer.close();
                 } finally {
                     r.close();
@@ -129,8 +138,7 @@ public class IndexMigration {
         }
         directoryManager.delete(index.getName());
         if (!directoryManager.rename(migrationName, index.getName())) {
-            throw new IOException("failed to move migrated directory " +
-                    migrationDir);
+            throw new IOException("failed to move migrated directory " + migrationDir);
         }
         log.info("Migrated " + index.getName());
     }
@@ -150,6 +158,17 @@ public class IndexMigration {
             this.oldSepChar = oldSepChar;
         }
 
+        @Override
+        public IndexReader[] getSequentialSubReaders() {
+            return null;
+        }
+
+        @Override
+        public FieldInfos getFieldInfos() {
+            return ReaderUtil.getMergedFieldInfos(in);
+        }
+
+        @Override
         public Document document(int n, FieldSelector fieldSelector)
                 throws CorruptIndexException, IOException {
             Document doc = super.document(n, fieldSelector);
@@ -167,12 +186,10 @@ public class IndexMigration {
             return doc;
         }
 
+        @Override
         public TermEnum terms() throws IOException {
             List<TermEnum> enums = new ArrayList<TermEnum>();
-            List<String> fieldNames = new ArrayList<String>();
-            for (Object obj : in.getFieldNames(FieldOption.ALL)) {
-                fieldNames.add((String) obj);
-            }
+            List<String> fieldNames = new ArrayList<String>(ReaderUtil.getIndexedFields(in));
             Collections.sort(fieldNames);
             for (String fieldName : fieldNames) {
                 if (fieldName.equals(FieldNames.PROPERTIES)) {
@@ -184,6 +201,7 @@ public class IndexMigration {
             return new MigrationTermEnum(new ChainedTermEnum(enums), oldSepChar);
         }
 
+        @Override
         public TermPositions termPositions() throws IOException {
             return new MigrationTermPositions(in.termPositions(), oldSepChar);
         }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationImpl.java
index 0f73aed..33e4a30 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationImpl.java
@@ -159,48 +159,37 @@ public class IndexingConfigurationImpl
                 for (int j = 0; j < childNodes.getLength(); j++) {
                     Node analyzerNode = childNodes.item(j);
                     if (analyzerNode.getNodeName().equals("analyzer")) {
-                        String analyzerClassName = analyzerNode.getAttributes().getNamedItem("class").getNodeValue();
-                        try {
-                            @SuppressWarnings("rawtypes")
-                            Class clazz = Class.forName(analyzerClassName);
-                            if (clazz == JackrabbitAnalyzer.class) {
-                                log.warn("Not allowed to configure " + JackrabbitAnalyzer.class.getName() +  " for a property. "
-                                        + "Using default analyzer for that property.");
-                            }
-                            else if (Analyzer.class.isAssignableFrom(clazz)) {
-                                Analyzer analyzer = (Analyzer) clazz.newInstance();
-                                NodeList propertyChildNodes = analyzerNode.getChildNodes();
-                                for (int k = 0; k < propertyChildNodes.getLength(); k++) {
-                                    Node propertyNode = propertyChildNodes.item(k);
-                                    if (propertyNode.getNodeName().equals("property")) {
-                                        // get property name
-                                        Name propName = resolver.getQName(getTextContent(propertyNode));
-                                        String fieldName = nsMappings.translateName(propName);
-                                        // set analyzer for the fulltext property fieldname
-                                        int idx = fieldName.indexOf(':');
-                                        fieldName = fieldName.substring(0, idx + 1)
-                                                    + FieldNames.FULLTEXT_PREFIX + fieldName.substring(idx + 1);
-                                        Object prevAnalyzer = analyzers.put(fieldName, analyzer);
-                                        if (prevAnalyzer != null) {
-                                            log.warn("Property " + propName.getLocalName()
-                                                    + " has been configured for multiple analyzers. "
-                                                    + " Last configured analyzer is used");
-                                        }
-                                    }
+                        Analyzer analyzer = JackrabbitAnalyzer.getAnalyzerInstance(
+                                analyzerNode.getAttributes().getNamedItem("class").getNodeValue());
+                        NodeList propertyChildNodes = analyzerNode.getChildNodes();
+                        for (int k = 0; k < propertyChildNodes.getLength(); k++) {
+                            Node propertyNode = propertyChildNodes.item(k);
+                            if (propertyNode.getNodeName().equals("property")) {
+                                // get property name
+                                Name propName = resolver.getQName(getTextContent(propertyNode));
+                                String fieldName = nsMappings.translateName(propName);
+                                // set analyzer for the fulltext property fieldname
+                                int idx = fieldName.indexOf(':');
+                                fieldName = fieldName.substring(0, idx + 1)
+                                        + FieldNames.FULLTEXT_PREFIX + fieldName.substring(idx + 1);
+                                Object prevAnalyzer = analyzers.put(fieldName, analyzer);
+                                if (prevAnalyzer != null) {
+                                    log.warn("Property " + propName.getLocalName()
+                                            + " has been configured for multiple analyzers. "
+                                            + " Last configured analyzer is used");
                                 }
-                            } else {
-                                log.warn("org.apache.lucene.analysis.Analyzer is not a superclass of "
-                                        + analyzerClassName + ". Ignoring this configure analyzer" );
                             }
-                        } catch (ClassNotFoundException e) {
-                            log.warn("Analyzer class not found: " + analyzerClassName, e);
                         }
                     }
                 }
             }
 
         }
-        aggregateRules = idxAggregates.toArray(new AggregateRule[idxAggregates.size()]);
+        if (idxAggregates.isEmpty()) {
+            aggregateRules = null;
+        } else {
+            aggregateRules = idxAggregates.toArray(new AggregateRule[idxAggregates.size()]);
+        }
     }
 
     /**
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitAnalyzer.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitAnalyzer.java
index 9a7fbee..3468744 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitAnalyzer.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitAnalyzer.java
@@ -18,12 +18,15 @@ package org.apache.jackrabbit.core.query.lucene;
 
 import java.io.IOException;
 import java.io.Reader;
+import java.lang.reflect.Constructor;
 import java.util.Collections;
 
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.TokenStream;
-import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.analysis.standard.ClassicAnalyzer;
 import org.apache.lucene.util.Version;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * This is the global jackrabbit lucene analyzer. By default, all
@@ -34,14 +37,68 @@ import org.apache.lucene.util.Version;
  * indexed with a specific analyzer. If configured, this analyzer is used to
  * index the text of the property and to parse searchtext for this property.
  */
+public class JackrabbitAnalyzer extends Analyzer {
 
-public class JackrabbitAnalyzer  extends Analyzer {
+    private static Logger log =
+            LoggerFactory.getLogger(JackrabbitAnalyzer.class);
+
+    private static final Analyzer DEFAULT_ANALYZER = new ClassicAnalyzer(
+            Version.LUCENE_36, Collections.emptySet());
 
     /**
-     * The default Jackrabbit analyzer if none is configured in <code><SearchIndex></code>
-     * configuration.
+     * Returns a new instance of the named Lucene {@link Analyzer} class,
+     * or the default analyzer if the given class can not be instantiated.
+     *
+     * @param className name of the analyzer class
+     * @return new analyzer instance, or the default analyzer
      */
-    private Analyzer defaultAnalyzer =  new StandardAnalyzer(Version.LUCENE_24, Collections.emptySet());
+    static Analyzer getAnalyzerInstance(String className) {
+        Class<?> analyzerClass;
+        try {
+            analyzerClass = Class.forName(className);
+        } catch (ClassNotFoundException e) {
+            log.warn(className + " could not be found", e);
+            return DEFAULT_ANALYZER;
+        }
+        if (!Analyzer.class.isAssignableFrom(analyzerClass)) {
+            log.warn(className + " is not a Lucene Analyzer");
+            return DEFAULT_ANALYZER;
+        } else if (JackrabbitAnalyzer.class.isAssignableFrom(analyzerClass)) {
+            log.warn(className + " can not be used as a JackrabbitAnalyzer component");
+            return DEFAULT_ANALYZER;
+        }
+
+        Exception cause = null;
+        Constructor<?>[] constructors = analyzerClass.getConstructors();
+        for (Constructor<?> constructor : constructors) {
+            Class<?>[] types = constructor.getParameterTypes();
+            if (types.length == 1 && types[0] == Version.class) {
+                try {
+                    return (Analyzer) constructor.newInstance(Version.LUCENE_36);
+                } catch (Exception e) {
+                    cause = e;
+                }
+            }
+        }
+        for (Constructor<?> constructor : constructors) {
+            if (constructor.getParameterTypes().length == 0) {
+                try {
+                    return (Analyzer) constructor.newInstance();
+                } catch (Exception e) {
+                    cause = e;
+                }
+            }
+        }
+
+        log.warn(className + " could not be instantiated", cause);
+        return DEFAULT_ANALYZER;
+    }
+
+    /**
+     * The default Jackrabbit analyzer if none is configured in
+     * <code><SearchIndex></code> configuration.
+     */
+    private Analyzer defaultAnalyzer = DEFAULT_ANALYZER;
 
     /**
      * The indexing configuration.
@@ -62,12 +119,20 @@ public class JackrabbitAnalyzer  extends Analyzer {
         defaultAnalyzer = analyzer;
     }
 
+    String getDefaultAnalyzerClass() {
+        return defaultAnalyzer.getClass().getName();
+    }
+
+    void setDefaultAnalyzerClass(String className) {
+        setDefaultAnalyzer(getAnalyzerInstance(className));
+    }
+
     /**
      * Creates a TokenStream which tokenizes all the text in the provided
      * Reader. If the fieldName (property) is configured to have a different
      * analyzer than the default, this analyzer is used for tokenization
      */
-    public TokenStream tokenStream(String fieldName, Reader reader) {
+    public final TokenStream tokenStream(String fieldName, Reader reader) {
         if (indexingConfig != null) {
             Analyzer propertyAnalyzer = indexingConfig.getPropertyAnalyzer(fieldName);
             if (propertyAnalyzer != null) {
@@ -78,7 +143,7 @@ public class JackrabbitAnalyzer  extends Analyzer {
     }
 
     @Override
-    public TokenStream reusableTokenStream(String fieldName, Reader reader)
+    public final TokenStream reusableTokenStream(String fieldName, Reader reader)
             throws IOException {
         if (indexingConfig != null) {
             Analyzer propertyAnalyzer = indexingConfig.getPropertyAnalyzer(fieldName);
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitQuery.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitQuery.java
index 624b20b..d7c7f44 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitQuery.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitQuery.java
@@ -33,7 +33,7 @@ public interface JackrabbitQuery {
     /**
      * Executes this query and returns {@link QueryHits} or <code>null</code> if
      * this query should be executed using the regular Lucene API.
-     * <p/>
+     * <p>
      * <b>Important note:</b> an implementation <b>must not</b> call
      * {@link JackrabbitIndexSearcher#execute(Query, Sort, long)}
      * with this query instance as a parameter, otherwise a stack overflow will
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitQueryParser.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitQueryParser.java
index 9f72c66..8df8675 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitQueryParser.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitQueryParser.java
@@ -141,11 +141,19 @@ public class JackrabbitQueryParser extends QueryParser {
      */
     protected Query getFieldQuery(String field, String queryText)
             throws ParseException {
+        return getFieldQuery(field, queryText, true);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected Query getFieldQuery(String field, String queryText, boolean quoted)
+            throws ParseException {
         if (queryText.startsWith("~")) {
             // synonym query
             return getSynonymQuery(field, queryText.substring(1));
         } else {
-            return super.getFieldQuery(field, queryText);
+            return super.getFieldQuery(field, queryText, quoted);
         }
     }
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitTermQuery.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitTermQuery.java
index 016aeda..bdb6794 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitTermQuery.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/JackrabbitTermQuery.java
@@ -40,7 +40,11 @@ public class JackrabbitTermQuery extends TermQuery {
     }
 
     public Weight createWeight(Searcher searcher) throws IOException {
-        return new JackrabbitTermWeight(searcher, super.createWeight(searcher));
+        // use a FilterSearcher to prevent per segment searches
+        // done by lucene. we handle that on our own
+        // see instanceof check in lucene TermWeight constructor
+        return new JackrabbitTermWeight(searcher,
+                super.createWeight(new FilterSearcher(searcher)));
     }
 
     /**
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LazyTextExtractorField.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LazyTextExtractorField.java
index 5568561..43bc231 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LazyTextExtractorField.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LazyTextExtractorField.java
@@ -30,10 +30,10 @@ import org.apache.lucene.document.Field.TermVector;
 import org.apache.tika.metadata.Metadata;
 import org.apache.tika.parser.ParseContext;
 import org.apache.tika.parser.Parser;
+import org.apache.tika.sax.BodyContentHandler;
+import org.apache.tika.sax.WriteOutContentHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
 
 /**
  * <code>LazyTextExtractorField</code> implements a Lucene field with a String
@@ -53,36 +53,37 @@ public class LazyTextExtractorField extends AbstractField {
         LoggerFactory.getLogger(LazyTextExtractorField.class);
 
     /**
-     * The exception used to forcibly terminate the extraction process
-     * when the maximum field length is reached.
-     */
-    private static final SAXException STOP =
-        new SAXException("max field length reached");
-
-    /**
      * The extracted text content of the given binary value.
      * Set to non-null when the text extraction task finishes.
      */
     private volatile String extract = null;
 
     /**
-     * Creates a new <code>LazyTextExtractorField</code> with the given
-     * <code>name</code>.
-     *
-     * @param name the name of the field.
-     * @param reader the reader where to obtain the string from.
-     * @param highlighting set to <code>true</code> to
-     *                     enable result highlighting support
+     * Creates a new <code>LazyTextExtractorField</code>.
+     * 
+     * @param parser
+     * @param value
+     * @param metadata
+     * @param executor
+     * @param highlighting
+     *            set to <code>true</code> to enable result highlighting support
+     * @param maxFieldLength
+     * @param withNorms
      */
     public LazyTextExtractorField(
             Parser parser, InternalValue value, Metadata metadata,
-            Executor executor, boolean highlighting, int maxFieldLength) {
+            Executor executor, boolean highlighting, int maxFieldLength,
+            boolean withNorms) {
         super(FieldNames.FULLTEXT,
                 highlighting ? Store.YES : Store.NO,
-                Field.Index.ANALYZED,
+                withNorms ? Field.Index.ANALYZED : Field.Index.ANALYZED_NO_NORMS,
                 highlighting ? TermVector.WITH_OFFSETS : TermVector.NO);
-        executor.execute(
-                new ParsingTask(parser, value, metadata, maxFieldLength));
+        executor.execute(new ParsingTask(parser, value, metadata,
+                maxFieldLength) {
+            public void setExtractedText(String value) {
+                LazyTextExtractorField.this.setExtractedText(value);
+            }
+        });
     }
 
     /**
@@ -148,7 +149,7 @@ public class LazyTextExtractorField extends AbstractField {
     /**
      * The background task for extracting text from a binary value.
      */
-    private class ParsingTask extends DefaultHandler implements LowPriorityTask {
+    abstract static class ParsingTask extends BodyContentHandler implements LowPriorityTask {
 
         private final Parser parser;
 
@@ -156,17 +157,21 @@ public class LazyTextExtractorField extends AbstractField {
 
         private final Metadata metadata;
 
-        private final int maxFieldLength;
+        private final WriteOutContentHandler writeOutContentHandler;
 
-        private final StringBuilder builder = new StringBuilder();
+        public ParsingTask(Parser parser, InternalValue value,
+                Metadata metadata, int maxFieldLength) {
+            this(new WriteOutContentHandler(maxFieldLength), parser, value,
+                    metadata);
+        }
 
-        public ParsingTask(
-                Parser parser, InternalValue value, Metadata metadata,
-                int maxFieldLength) {
+        private ParsingTask(WriteOutContentHandler writeOutContentHandler,
+                Parser parser, InternalValue value, Metadata metadata) {
+            super(writeOutContentHandler);
+            this.writeOutContentHandler = writeOutContentHandler;
             this.parser = parser;
             this.value = value;
             this.metadata = metadata;
-            this.maxFieldLength = maxFieldLength;
         }
 
         public void run() {
@@ -185,36 +190,20 @@ public class LazyTextExtractorField extends AbstractField {
             } catch (Throwable t) {
                 // Capture and report any other full text extraction problems.
                 // The special STOP exception is used for normal termination.
-                if (t != STOP) {
+                if (!writeOutContentHandler.isWriteLimitReached(t)) {
                     log.debug("Failed to extract text from a binary property."
                             + " This is a fairly common case, and nothing to"
                             + " worry about. The stack trace is included to"
                             + " help improve the text extraction feature.", t);
-                    builder.replace(0, builder.length(), "TextExtractionError");
+                    setExtractedText("TextExtractionError");
+                    return;
                 }
             } finally {
                 value.discard();
             }
-            setExtractedText(builder.toString());
-        }
-
-        @Override
-        public void characters(char[] ch, int start, int length)
-                throws SAXException {
-            builder.append(
-                    ch, start,
-                    Math.min(length, maxFieldLength - builder.length()));
-            if (builder.length() >= maxFieldLength) {
-                throw STOP;
-            }
-        }
-
-        @Override
-        public void ignorableWhitespace(char[] ch, int start, int length)
-                throws SAXException {
-            characters(ch, start, length);
+            setExtractedText(writeOutContentHandler.toString());
         }
 
+        protected abstract void setExtractedText(String value);
     }
-
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java
index be33010..be35057 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java
@@ -938,7 +938,7 @@ public class LuceneQueryBuilder implements QueryNodeVisitor {
                         if (steps.length == 2) {
                             selectParent = false;
                         }
-                    } else if (step instanceof LocationStepQueryNode) {
+                    } else if (step != null) {
                         // join name test with property query if there is one
                         if (name != null) {
                             if (!name.equals(PARENT_ELEMENT_NAME)) {
@@ -982,7 +982,7 @@ public class LuceneQueryBuilder implements QueryNodeVisitor {
                                                indexFormatVersion,
                                                nsMappings);
                 } else {
-                    if (step instanceof LocationStepQueryNode) {
+                    if (step != null) {
                         query = new ParentAxisQuery(query, name, indexFormatVersion, nsMappings);
                     } else {
                         throw new UnsupportedOperationException();
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryFactory.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryFactory.java
index c0c9a45..e956ae0 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryFactory.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryFactory.java
@@ -306,7 +306,7 @@ public class LuceneQueryFactory {
     /**
      * Creates a lucene query for the given QOM full text search.
      *
-     * @param constraint the full text search constraint.
+     * @param fts the full text search constraint.
      * @return the lucene query for the given constraint.
      * @throws RepositoryException if an error occurs while creating the query.
      */
@@ -459,7 +459,6 @@ public class LuceneQueryFactory {
             ids.add(ancestor.getIdentifier());
             while (!ids.isEmpty()) {
                 String id = ids.removeFirst();
-                clauses++;
                 Query q = new JackrabbitTermQuery(new Term(FieldNames.PARENT, id));
                 QueryHits hits = searcher.evaluate(q);
                 ScoreNode sn = hits.nextScoreNode();
@@ -467,10 +466,12 @@ public class LuceneQueryFactory {
                     // reset query so it does not overflow because of the max
                     // clause count condition,
                     // see JCR-3108
+                    clauses++;
                     if (clauses == BooleanQuery.getMaxClauseCount()) {
                         BooleanQuery wrapQ = new BooleanQuery();
                         wrapQ.add(query, SHOULD);
                         query = wrapQ;
+                        clauses = 1;
                     }
                     query.add(q, SHOULD);
                     do {
@@ -668,9 +669,7 @@ public class LuceneQueryFactory {
 
     protected Query getNodeLocalNameQuery(int transform, String operator,
             StaticOperand right) throws RepositoryException {
-        if (transform != TRANSFORM_NONE
-                || (!JCR_OPERATOR_EQUAL_TO.equals(operator) && !JCR_OPERATOR_LIKE
-                        .equals(operator))) {
+        if (!JCR_OPERATOR_EQUAL_TO.equals(operator) && !JCR_OPERATOR_LIKE.equals(operator)) {
             throw new UnsupportedRepositoryOperationException();
         }
         String name = evaluator.getValue(right).getString();
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryHits.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryHits.java
index 26515a6..9b102f0 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryHits.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryHits.java
@@ -49,7 +49,7 @@ public class LuceneQueryHits implements QueryHits {
         this.reader = reader;
         // We rely on Scorer#nextDoc() and Scorer#advance(int) so enable
         // scoreDocsInOrder
-        this.scorer = query.weight(searcher).scorer(reader, true, false);
+        this.scorer = query.createWeight(searcher).scorer(reader, true, false);
     }
 
     /**
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/MatchAllQuery.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/MatchAllQuery.java
index 891ab7b..4a288b4 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/MatchAllQuery.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/MatchAllQuery.java
@@ -36,7 +36,7 @@ class MatchAllQuery extends Query {
 
     /**
      * Creates a new <code>MatchAllQuery</code> .
-     * <p/>
+     * <p>
      *
      * @param field the field name.
      * @throws NullPointerException if <code>field</code> is null.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/MoreLikeThis.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/MoreLikeThis.java
index 8252beb..8e518b9 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/MoreLikeThis.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/MoreLikeThis.java
@@ -17,6 +17,7 @@
 package org.apache.jackrabbit.core.query.lucene;
 
 import org.apache.lucene.util.PriorityQueue;
+import org.apache.lucene.util.ReaderUtil;
 import org.apache.lucene.util.Version;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.Term;
@@ -154,7 +155,7 @@ public final class MoreLikeThis {
      * Default analyzer to parse source doc with.
      * @see #getAnalyzer
      */
-    public static final Analyzer DEFAULT_ANALYZER = new StandardAnalyzer(Version.LUCENE_24);
+    public static final Analyzer DEFAULT_ANALYZER = new StandardAnalyzer(Version.LUCENE_36);
 
     /**
      * Ignore terms with less than this frequency in the source doc.
@@ -506,7 +507,7 @@ public final class MoreLikeThis {
     public Query like(int docNum) throws IOException {
         if (fieldNames == null) {
             // gather list of valid fields from lucene
-            Collection<String> fields = ir.getFieldNames(IndexReader.FieldOption.INDEXED);
+            Collection<String> fields = ReaderUtil.getIndexedFields(ir);
             fieldNames = fields.toArray(new String[fields.size()]);
         }
 
@@ -521,7 +522,7 @@ public final class MoreLikeThis {
     public Query like(File f) throws IOException {
         if (fieldNames == null) {
             // gather list of valid fields from lucene
-            Collection<String> fields = ir.getFieldNames(IndexReader.FieldOption.INDEXED);
+            Collection<String> fields = ReaderUtil.getIndexedFields(ir);
             fieldNames = fields.toArray(new String[fields.size()]);
         }
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/MultiIndex.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/MultiIndex.java
index 3d939e3..0eacf22 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/MultiIndex.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/MultiIndex.java
@@ -66,15 +66,15 @@ import org.slf4j.LoggerFactory;
  * persistent indexes. Further operations on the new persistent index will
  * however only require an <code>IndexReader</code> which serves for queries
  * but also for delete operations on the index.
- * <p/>
+ * <p>
  * The persistent indexes are merged from time to time. The merge behaviour
  * is configurable using the methods: {@link SearchIndex#setMaxMergeDocs(int)},
  * {@link SearchIndex#setMergeFactor(int)} and {@link SearchIndex#setMinMergeDocs(int)}.
  * For detailed description of the configuration parameters see also the lucene
  * <code>IndexWriter</code> class.
- * <p/>
+ * <p>
  * This class is thread-safe.
- * <p/>
+ * <p>
  * Note on implementation: Multiple modifying threads are synchronized on a
  * <code>MultiIndex</code> instance itself. Synchronization between a modifying
  * thread and reader threads is done using {@link #updateMonitor} and
@@ -287,7 +287,6 @@ public class MultiIndex {
                     handler.getTextAnalyzer(), handler.getSimilarity(),
                     cache, indexingQueue, directoryManager,
                     handler.getMaxHistoryAge());
-            index.setMaxFieldLength(handler.getMaxFieldLength());
             index.setUseCompoundFile(handler.getUseCompoundFile());
             index.setTermInfosIndexDivisor(handler.getTermInfosIndexDivisor());
             indexes.add(index);
@@ -525,7 +524,7 @@ public class MultiIndex {
      * <code>indexNames</code>. An <code>IndexListener</code> is registered and
      * notified when documents are deleted from one of the indexes in
      * <code>indexNames</code>.
-     * <p/>
+     * <p>
      * Note: the number of <code>IndexReaders</code> returned by this method is
      * not necessarily the same as the number of index names passed. An index
      * might have been deleted and is not reachable anymore.
@@ -601,7 +600,6 @@ public class MultiIndex {
             }
             throw e;
         }
-        index.setMaxFieldLength(handler.getMaxFieldLength());
         index.setUseCompoundFile(handler.getUseCompoundFile());
         index.setTermInfosIndexDivisor(handler.getTermInfosIndexDivisor());
 
@@ -776,8 +774,7 @@ public class MultiIndex {
      * @throws IOException if an error occurs while running the check.
      */
     ConsistencyCheck runConsistencyCheck() throws IOException {
-        return ConsistencyCheck.run(this,
-                handler.getContext().getItemStateManager());
+        return ConsistencyCheck.run(this, handler, excludedIDs);
     }
 
     /**
@@ -897,7 +894,7 @@ public class MultiIndex {
      * Removes the <code>index</code> from the list of active sub indexes.
      * Depending on the {@link SearchIndex#getMaxHistoryAge()}, the
      * Index is not deleted right away.
-     * <p/>
+     * <p>
      * This method does not close the index, but rather expects that the index
      * has already been closed.
      *
@@ -988,7 +985,7 @@ public class MultiIndex {
      * reader is already <code>null</code> this method does nothing. When this
      * method returns {@link #multiReader} is guaranteed to be <code>null</code>
      * even if an exception is thrown.
-     * <p/>
+     * <p>
      * Please note that this method does not take care of any synchronization.
      * A caller must ensure that it is the only thread operating on this multi
      * index, or that it holds the {@link #updateMonitor}.
@@ -1101,10 +1098,13 @@ public class MultiIndex {
      * @throws IOException if the volatile index cannot be reset.
      */
     private void resetVolatileIndex() throws IOException {
+        // JCR-3227 close VolatileIndex properly
+        if (volatileIndex != null) {
+            volatileIndex.close();
+        }
         volatileIndex = new VolatileIndex(handler.getTextAnalyzer(),
                 handler.getSimilarity(), indexingQueue);
         volatileIndex.setUseCompoundFile(handler.getUseCompoundFile());
-        volatileIndex.setMaxFieldLength(handler.getMaxFieldLength());
         volatileIndex.setBufferSize(handler.getBufferSize());
     }
 
@@ -1233,6 +1233,10 @@ public class MultiIndex {
             } catch (NoSuchItemStateException e) {
                 handler.getOnWorkspaceInconsistencyHandler().handleMissingChildNode(
                         e, handler, path, node, child);
+            } catch (ItemStateException e) {
+                // JCR-3268 log bundle corruption and continue
+                handler.getOnWorkspaceInconsistencyHandler().logError(e,
+                        handler, childPath, node, child);
             }
             if (childState != null) {
                 count = createIndex(childState, childPath, stateMgr, count);
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/MultiScorer.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/MultiScorer.java
index 63b004b..f882eb2 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/MultiScorer.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/MultiScorer.java
@@ -65,7 +65,7 @@ class MultiScorer extends Scorer {
     @Override
     public int nextDoc() throws IOException {
         while (currentDoc != NO_MORE_DOCS) {
-            if (scorers[currentScorer].nextDoc() != NO_MORE_DOCS) {
+            if (scorers[currentScorer] != null && scorers[currentScorer].nextDoc() != NO_MORE_DOCS) {
                 currentDoc = scorers[currentScorer].docID() + starts[currentScorer];
                 return currentDoc;
             } else if (++currentScorer < scorers.length) {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NodeIndexer.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NodeIndexer.java
index 02f7087..a224ec8 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NodeIndexer.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NodeIndexer.java
@@ -44,7 +44,10 @@ import org.apache.jackrabbit.spi.commons.name.NameConstants;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
 import org.apache.lucene.document.Fieldable;
+import org.apache.lucene.index.FieldInfo;
 import org.apache.tika.metadata.Metadata;
+import org.apache.tika.mime.MediaType;
+import org.apache.tika.parser.ParseContext;
 import org.apache.tika.parser.Parser;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -62,7 +65,7 @@ public class NodeIndexer {
     /**
      * The default boost for a lucene field: 1.0f.
      */
-    protected static final float DEFAULT_BOOST = 1.0f;
+    protected static final float DEFAULT_BOOST = IndexingConfiguration.DEFAULT_BOOST;
 
     /**
      * The <code>NodeState</code> of the node to index
@@ -97,6 +100,11 @@ public class NodeIndexer {
     private final Parser parser;
 
     /**
+     * The media types supported by the parser used.
+     */
+    private Set<MediaType> supportedMediaTypes;
+
+    /**
      * The indexing configuration or <code>null</code> if none is available.
      */
     protected IndexingConfiguration indexingConfig;
@@ -217,9 +225,11 @@ public class NodeIndexer {
             // parent UUID
             if (node.getParentId() == null) {
                 // root node
-                doc.add(new Field(FieldNames.PARENT, false, "",
+                Field parent = new Field(FieldNames.PARENT, false, "",
                         Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS,
-                        Field.TermVector.NO));
+                        Field.TermVector.NO);
+                parent.setIndexOptions(FieldInfo.IndexOptions.DOCS_ONLY);
+                doc.add(parent);
                 addNodeName(doc, "", "");
             } else if (node.getSharedSet().isEmpty()) {
                 addParentChildRelation(doc, node.getParentId());
@@ -244,28 +254,32 @@ public class NodeIndexer {
 
         Set<Name> props = node.getPropertyNames();
         for (Name propName : props) {
-            PropertyId id = new PropertyId(node.getNodeId(), propName);
-            try {
-                PropertyState propState = (PropertyState) stateProvider.getItemState(id);
-
-                // add each property to the _PROPERTIES_SET for searching
-                // beginning with V2
-                if (indexFormatVersion.getVersion() >= IndexFormatVersion.V2.getVersion()) {
-                    addPropertyName(doc, propState.getName());
-                }
+            if (isIndexed(propName)) {
+                PropertyId id = new PropertyId(node.getNodeId(), propName);
+                try {
+                    PropertyState propState =
+                            (PropertyState) stateProvider.getItemState(id);
+
+                    // add each property to the _PROPERTIES_SET for searching
+                    // beginning with V2
+                    if (indexFormatVersion.getVersion() >= IndexFormatVersion.V2.getVersion()) {
+                        addPropertyName(doc, propState.getName());
+                    }
 
-                InternalValue[] values = propState.getValues();
-                for (InternalValue value : values) {
-                    addValue(doc, value, propState.getName());
-                }
-                if (values.length > 1) {
-                    // real multi-valued
-                    addMVPName(doc, propState.getName());
+                    InternalValue[] values = propState.getValues();
+                    for (InternalValue value : values) {
+                        addValue(doc, value, propState.getName());
+                    }
+
+                    if (values.length > 1) {
+                        // real multi-valued
+                        addMVPName(doc, propState.getName());
+                    }
+                } catch (NoSuchItemStateException e) {
+                    throwRepositoryException(e);
+                } catch (ItemStateException e) {
+                    throwRepositoryException(e);
                 }
-            } catch (NoSuchItemStateException e) {
-                throwRepositoryException(e);
-            } catch (ItemStateException e) {
-                throwRepositoryException(e);
             }
         }
 
@@ -322,76 +336,48 @@ public class NodeIndexer {
         }
         switch (value.getType()) {
             case PropertyType.BINARY:
-                if (isIndexed(name)) {
-                    addBinaryValue(doc, fieldName, value);
-                }
+                addBinaryValue(doc, fieldName, value);
                 break;
             case PropertyType.BOOLEAN:
-                if (isIndexed(name)) {
-                    addBooleanValue(doc, fieldName, value.getBoolean());
-                }
+                addBooleanValue(doc, fieldName, value.getBoolean());
                 break;
             case PropertyType.DATE:
-                if (isIndexed(name)) {
-                    addCalendarValue(doc, fieldName, value.getDate());
-                }
+                addCalendarValue(doc, fieldName, value.getDate());
                 break;
             case PropertyType.DOUBLE:
-                if (isIndexed(name)) {
-                    addDoubleValue(doc, fieldName, value.getDouble());
-                }
+                addDoubleValue(doc, fieldName, value.getDouble());
                 break;
             case PropertyType.LONG:
-                if (isIndexed(name)) {
-                    addLongValue(doc, fieldName, value.getLong());
-                }
+                addLongValue(doc, fieldName, value.getLong());
                 break;
             case PropertyType.REFERENCE:
-                if (isIndexed(name)) {
-                    addReferenceValue(doc, fieldName, value.getNodeId(), false);
-                }
+                addReferenceValue(doc, fieldName, value.getNodeId(), false);
                 break;
             case PropertyType.WEAKREFERENCE:
-                if (isIndexed(name)) {
-                    addReferenceValue(doc, fieldName, value.getNodeId(), true);
-                }
+                addReferenceValue(doc, fieldName, value.getNodeId(), true);
                 break;
             case PropertyType.PATH:
-                if (isIndexed(name)) {
-                    addPathValue(doc, fieldName, value.getPath());
-                }
+                addPathValue(doc, fieldName, value.getPath());
                 break;
             case PropertyType.URI:
-                if (isIndexed(name)) {
-                    addURIValue(doc, fieldName, value.getURI());
-                }
+                addURIValue(doc, fieldName, value.getURI());
                 break;
             case PropertyType.STRING:
-                if (isIndexed(name)) {
-                    // never fulltext index jcr:uuid String
-                    if (name.equals(NameConstants.JCR_UUID)) {
-                        addStringValue(doc, fieldName, value.getString(),
-                                false, false, DEFAULT_BOOST, true);
-                    } else {
-                        addStringValue(doc, fieldName, value.getString(),
-                                true, isIncludedInNodeIndex(name),
-                                getPropertyBoost(name), useInExcerpt(name));
-                    }
+                // never fulltext index jcr:uuid String
+                if (name.equals(NameConstants.JCR_UUID)) {
+                    addStringValue(doc, fieldName, value.getString(),
+                            false, false, DEFAULT_BOOST, true);
+                } else {
+                    addStringValue(doc, fieldName, value.getString(),
+                            true, isIncludedInNodeIndex(name),
+                            getPropertyBoost(name), useInExcerpt(name));
                 }
                 break;
             case PropertyType.NAME:
-                // jcr:primaryType and jcr:mixinTypes are required for correct
-                // node type resolution in queries
-                if (name.equals(NameConstants.JCR_PRIMARYTYPE)
-                        || name.equals(NameConstants.JCR_MIXINTYPES)
-                        || isIndexed(name)) {
-                    addNameValue(doc, fieldName, value.getName());
-                }
+                addNameValue(doc, fieldName, value.getName());
                 break;
             case PropertyType.DECIMAL:
-                if (isIndexed(name)) {
-                    addDecimalValue(doc, fieldName, value.getDecimal());
-                }
+                addDecimalValue(doc, fieldName, value.getDecimal());
                 break;
             default:
                 throw new IllegalArgumentException("illegal internal value type: " + value.getType());
@@ -413,12 +399,6 @@ public class NodeIndexer {
      */
     protected void addValueProperty(Document doc, InternalValue value,
             Name name, String fieldName) throws RepositoryException {
-
-        // skip this method if field is not indexed
-        if (!isIndexed(name)) {
-            return;
-        }
-
         // add length
         if (indexFormatVersion.getVersion() >= IndexFormatVersion.V3.getVersion()) {
             addLength(doc, fieldName, value);
@@ -445,10 +425,10 @@ public class NodeIndexer {
 
     /**
      * Adds the binary value to the document as the named field.
-     * <p/>
+     * <p>
      * This implementation checks if this {@link #node} is of type nt:resource
      * and if that is the case, tries to extract text from the binary property
-     * using the {@link #extractor}.
+     * using the {@link #parser}.
      *
      * @param doc           The document to which to add the field
      * @param fieldName     The name of the field to add
@@ -466,7 +446,7 @@ public class NodeIndexer {
             }
 
             InternalValue type = getValue(NameConstants.JCR_MIMETYPE);
-            if (type != null) {
+            if (type != null && isSupportedMediaType(type.getString())) {
                 Metadata metadata = new Metadata();
                 metadata.set(Metadata.CONTENT_TYPE, type.getString());
 
@@ -477,7 +457,7 @@ public class NodeIndexer {
                             Metadata.CONTENT_ENCODING, encoding.getString());
                 }
 
-                doc.add(createFulltextField(internalValue, metadata));
+                doc.add(createFulltextField(internalValue, metadata, false));
             }
         } catch (Throwable t) {
             // TODO: How to recover from a transient indexing failure?
@@ -681,7 +661,7 @@ public class NodeIndexer {
      * @param doc           The document to which to add the field
      * @param fieldName     The name of the field to add
      * @param internalValue The value for the field to add to the document.
-     * @deprecated Use {@link #addStringValue(Document, String, Object, boolean)
+     * @deprecated Use {@link #addStringValue(Document, String, String, boolean)
      *             addStringValue(Document, String, Object, boolean)} instead.
      */
     protected void addStringValue(Document doc, String fieldName, String internalValue) {
@@ -719,7 +699,7 @@ public class NodeIndexer {
      *                           tokenized and added to the node scope fulltext
      *                           index.
      * @param boost              the boost value for this string field.
-     * @deprecated use {@link #addStringValue(Document, String, Object, boolean, boolean, float, boolean)} instead.
+     * @deprecated use {@link #addStringValue(Document, String, String, boolean, boolean, float, boolean)} instead.
      */
     protected void addStringValue(Document doc, String fieldName,
                                   String internalValue, boolean tokenized,
@@ -761,15 +741,18 @@ public class NodeIndexer {
             int idx = fieldName.indexOf(':');
             fieldName = fieldName.substring(0, idx + 1)
                     + FieldNames.FULLTEXT_PREFIX + fieldName.substring(idx + 1);
+            boolean hasNorms = boost != DEFAULT_BOOST;
+            Field.Index indexType = hasNorms ? Field.Index.ANALYZED
+                    : Field.Index.ANALYZED_NO_NORMS;
             Field f = new Field(fieldName, true, internalValue, Field.Store.NO,
-                    Field.Index.ANALYZED, Field.TermVector.NO);
+                    indexType, Field.TermVector.NO);
             f.setBoost(boost);
             doc.add(f);
 
             if (includeInNodeIndex) {
                 // also create fulltext index of this value
                 boolean store = supportHighlighting && useInExcerpt;
-                f = createFulltextField(internalValue, store, supportHighlighting);
+                f = createFulltextField(internalValue, store, supportHighlighting, hasNorms);
                 if (useInExcerpt) {
                     doc.add(f);
                 } else {
@@ -805,7 +788,7 @@ public class NodeIndexer {
      *
      * @param value the string value.
      * @return a lucene field.
-     * @deprecated use {@link #createFulltextField(String, boolean, boolean)} instead.
+     * @deprecated use {@link #createFulltextField(String, boolean, boolean, boolean)} instead.
      */
     protected Field createFulltextField(String value) {
         return createFulltextField(value, supportHighlighting, supportHighlighting);
@@ -813,30 +796,53 @@ public class NodeIndexer {
 
     /**
      * Creates a fulltext field for the string <code>value</code>.
-     *
+     * 
      * @param value the string value.
      * @param store if the value of the field should be stored.
      * @param withOffsets if a term vector with offsets should be stored.
      * @return a lucene field.
+     * @deprecated use {@link #createFulltextField(String, boolean, boolean, boolean)} instead.
      */
     protected Field createFulltextField(String value,
                                         boolean store,
                                         boolean withOffsets) {
+        return createFulltextField(value, store, withOffsets, true);
+    }
+
+    /**
+     * Creates a fulltext field for the string <code>value</code>.
+     *
+     * @param value the string value.
+     * @param store if the value of the field should be stored.
+     * @param withOffsets if a term vector with offsets should be stored.
+     * @param withNorms if norm information should be added for this value
+     * @return a lucene field.
+     */
+    protected Field createFulltextField(String value,
+                                        boolean store,
+                                        boolean withOffsets,
+                                        boolean withNorms) {
         Field.TermVector tv;
         if (withOffsets) {
             tv = Field.TermVector.WITH_OFFSETS;
         } else {
             tv = Field.TermVector.NO;
         }
+        Field.Index index;
+        if (withNorms) {
+            index = Field.Index.ANALYZED;
+        } else {
+            index = Field.Index.ANALYZED_NO_NORMS;
+        }
         if (store) {
             // We would be able to store the field compressed or not depending
             // on a criterion but then we could not determine later is this field
             // has been compressed or not, so we choose to store it uncompressed
             return new Field(FieldNames.FULLTEXT, false, value, Field.Store.YES,
-                    Field.Index.ANALYZED, tv);
+                    index, tv);
         } else {
             return new Field(FieldNames.FULLTEXT, false, value,
-                    Field.Store.NO, Field.Index.ANALYZED, tv);
+                    Field.Store.NO, index, tv);
         }
     }
 
@@ -846,28 +852,43 @@ public class NodeIndexer {
      * @param value the binary value
      * @param metadata document metatadata
      * @return a lucene field.
+     * @deprecated use {@link #createFulltextField(InternalValue, Metadata, boolean)} instead.
      */
     protected Fieldable createFulltextField(
             InternalValue value, Metadata metadata) {
-        return new LazyTextExtractorField(
-                parser, value, metadata, executor,
-                supportHighlighting, getMaxExtractLength());
+        return createFulltextField(value, metadata, true);
     }
 
     /**
-     * Returns <code>true</code> if the property with the given name should be
-     * indexed.
+     * Creates a fulltext field for the reader <code>value</code>.
+     *
+     * @param value the binary value
+     * @param metadata document metatadata
+     * @param withNorms if norm information should be added for this value
+     * @return a lucene field.
+     */
+    protected Fieldable createFulltextField(
+            InternalValue value, Metadata metadata, boolean withNorms) {
+        return new LazyTextExtractorField(parser, value, metadata, executor,
+                supportHighlighting, getMaxExtractLength(), withNorms);
+    }
+
+    /**
+     * Returns <code>true</code> if the property with the given name should
+     * be indexed. The default is to index all properties unless explicit
+     * indexing configuration is specified. The <code>jcr:primaryType</code>
+     * and <code>jcr:mixinTypes</code> properties are always indexed for
+     * correct node type resolution in queries.
      *
      * @param propertyName name of a property.
-     * @return <code>true</code> if the property should be fulltext indexed;
+     * @return <code>true</code> if the property should be indexed;
      *         <code>false</code> otherwise.
      */
     protected boolean isIndexed(Name propertyName) {
-        if (indexingConfig == null) {
-            return true;
-        } else {
-            return indexingConfig.isIndexed(node, propertyName);
-        }
+        return indexingConfig == null
+                || propertyName.equals(NameConstants.JCR_PRIMARYTYPE)
+                || propertyName.equals(NameConstants.JCR_MIXINTYPES)
+                || indexingConfig.isIndexed(node, propertyName);
     }
 
     /**
@@ -903,6 +924,20 @@ public class NodeIndexer {
     }
 
     /**
+     * Returns <code>true</code> if the provided type is among the types
+     * supported by the Tika parser we are using.
+     *
+     * @param type  the type to check.
+     * @return whether the type is supported by the Tika parser we are using.
+     */
+    protected boolean isSupportedMediaType(final String type) {
+        if (supportedMediaTypes == null) {
+            supportedMediaTypes = parser.getSupportedTypes(new ParseContext());
+        }
+        return supportedMediaTypes.contains(MediaType.parse(type));
+    }
+
+    /**
      * Returns the boost value for the given property name.
      *
      * @param propertyName the name of a property.
@@ -983,9 +1018,11 @@ public class NodeIndexer {
     protected void addParentChildRelation(Document doc,
                                           NodeId parentId)
             throws ItemStateException, RepositoryException {
-        doc.add(new Field(FieldNames.PARENT, false, parentId.toString(),
-                Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS,
-                Field.TermVector.NO));
+        Field parentField = new Field(FieldNames.PARENT, false,
+                parentId.toString(), Field.Store.YES,
+                Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO);
+        parentField.setIndexOptions(FieldInfo.IndexOptions.DOCS_ONLY);
+        doc.add(parentField);
         NodeState parent = (NodeState) stateProvider.getItemState(parentId);
         ChildNodeEntry child = parent.getChildNodeEntry(node.getNodeId());
         if (child == null) {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NodeIteratorImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NodeIteratorImpl.java
index ad75d66..61a6a6f 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NodeIteratorImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NodeIteratorImpl.java
@@ -179,6 +179,7 @@ class NodeIteratorImpl implements NodeIterator {
      */
     protected void fetchNext() {
         try {
+            next = null; // reset
             sessionContext.getSessionState().perform(new FetchNext());
         } catch (RepositoryException e) {
             log.warn("Failed to fetch next node", e);
@@ -188,7 +189,6 @@ class NodeIteratorImpl implements NodeIterator {
     private class FetchNext implements SessionOperation<Object> {
 
         public Object perform(SessionContext context) {
-            next = null; // reset
 
             ItemManager itemMgr = context.getItemManager();
             while (next == null && scoreNodes.hasNext()) {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NormalizeSortComparator.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NormalizeSortComparator.java
new file mode 100644
index 0000000..e877570
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NormalizeSortComparator.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.query.lucene;
+
+import java.io.IOException;
+
+import org.apache.lucene.analysis.ASCIIFoldingFilter;
+import org.apache.lucene.search.FieldComparator;
+import org.apache.lucene.search.FieldComparatorSource;
+
+/**
+ * <code>NormalizeSortComparator</code> implements a <code>FieldComparator</code> which
+ * compares the lower-cased and normalized string values of a base sort comparator.
+ */
+public class NormalizeSortComparator extends FieldComparatorSource {
+
+    /**
+     * The base sort comparator.
+     */
+    private final FieldComparatorSource base;
+
+    /**
+     * Creates a new upper case sort comparator.
+     *
+     * @param base the base sort comparator source.
+     */
+    public NormalizeSortComparator(FieldComparatorSource base)  {
+        this.base = base;
+    }
+
+    @Override
+    public FieldComparator<?> newComparator(
+            String fieldname, int numHits, int sortPos, boolean reversed)
+            throws IOException {
+        FieldComparator<?> comparator =
+                base.newComparator(fieldname, numHits, sortPos, reversed);
+        assert comparator instanceof FieldComparatorBase;
+
+        return new FieldComparatorDecorator((FieldComparatorBase) comparator) {
+            @Override
+            protected Comparable<?> sortValue(int doc) {
+                Comparable<?> comparable = super.sortValue(doc);
+                if (comparable != null) {
+                    char[] input = comparable.toString().toCharArray();
+
+                    // Normalize to ASCII using Lucene's ASCIIFoldingFilter
+                    char[] output = new char[input.length * 4]; // worst-case
+                    int length = ASCIIFoldingFilter.foldToASCII(
+                            input, 0, output, 0, input.length);
+
+                    // Convert to lower case, and check if output is different
+                    boolean different = length != input.length;
+                    for (int i = 0; i < length; i++) {
+                        char c = output[i];
+                        if ('A'<= c && c <= 'Z') {
+                            output[i] = (char) (c + 'a' - 'A');
+                        }
+                        if (!different && input[i] != output[i]) {
+                            different = true;
+                        }
+                    }
+
+                    if (different) {
+                        comparable = new String(output, 0, length);
+                    }
+                }
+                return comparable;
+            }
+        };
+    }
+
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NotQuery.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NotQuery.java
index 8cde1f5..7446bdc 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NotQuery.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NotQuery.java
@@ -139,6 +139,12 @@ class NotQuery extends Query {
         public Scorer scorer(IndexReader reader, boolean scoreDocsInOrder,
                 boolean topScorer) throws IOException {
             contextScorer = context.weight(searcher).scorer(reader, scoreDocsInOrder, topScorer);
+            if (contextScorer == null) {
+                // context query does not match any node
+                // the inverse is to match all nodes
+                return new MatchAllDocsQuery().createWeight(searcher).scorer(
+                        reader, scoreDocsInOrder, false);
+            }
             return new NotQueryScorer(reader);
         }
 
@@ -171,6 +177,8 @@ class NotQuery extends Query {
          */
         private int contextNo = -1;
 
+        private boolean firstTime = true;
+
         /**
          * Creates a new scorer
          * @param reader
@@ -186,7 +194,8 @@ class NotQuery extends Query {
                 return docNo;
             }
 
-            if (docNo == -1) {
+            if (firstTime) {
+                firstTime = false;
                 // get first doc of context scorer
                 int docId = contextScorer.nextDoc();
                 if (docId != NO_MORE_DOCS) {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ParentAxisQuery.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ParentAxisQuery.java
index ea76775..03765d2 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ParentAxisQuery.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ParentAxisQuery.java
@@ -190,7 +190,7 @@ class ParentAxisQuery extends Query {
          */
         public Scorer scorer(IndexReader reader, boolean scoreDocsInOrder,
                 boolean topScorer) throws IOException {
-            contextScorer = contextQuery.weight(searcher).scorer(reader, scoreDocsInOrder, topScorer);
+            contextScorer = contextQuery.weight(searcher).scorer(reader, scoreDocsInOrder, false);
             HierarchyResolver resolver = (HierarchyResolver) reader;
             return new ParentAxisScorer(searcher.getSimilarity(),
                     reader, searcher, resolver);
@@ -235,7 +235,7 @@ class ParentAxisQuery extends Query {
          * Map that contains the scores from matching documents from the context
          * query. To save memory only scores that are not equal to the score
          * value of the first match are put to this map.
-         * <p/>
+         * <p>
          * key=[Integer] id of selected document from context query<br>
          * value=[Float] score for that document
          */
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/PersistentIndex.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/PersistentIndex.java
index 6f5696c..c8bbf60 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/PersistentIndex.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/PersistentIndex.java
@@ -104,7 +104,7 @@ class PersistentIndex extends AbstractIndex {
     /**
      * Merges the provided indexes into this index. After this completes, the
      * index is optimized.
-     * <p/>
+     * <p>
      * The provided IndexReaders are not closed.
      *
      * @param readers the readers of indexes to add.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/PredicateDerefQuery.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/PredicateDerefQuery.java
index e106f94..b09d829 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/PredicateDerefQuery.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/PredicateDerefQuery.java
@@ -202,9 +202,9 @@ public class PredicateDerefQuery extends Query {
          */
         public Scorer scorer(IndexReader reader, boolean scoreDocsInOrder,
                 boolean topScorer) throws IOException {
-            subQueryScorer = subQuery.weight(searcher).scorer(reader, scoreDocsInOrder, topScorer);
+            subQueryScorer = subQuery.weight(searcher).scorer(reader, scoreDocsInOrder, false);
             if (nameTest != null) {
-                nameTestScorer = new NameQuery(nameTest, version, nsMappings).weight(searcher).scorer(reader, scoreDocsInOrder, topScorer);
+                nameTestScorer = new NameQuery(nameTest, version, nsMappings).weight(searcher).scorer(reader, scoreDocsInOrder, false);
             }
             return new DerefScorer(searcher.getSimilarity(), reader);
         }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java
index 9d18ed4..6fe9220 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java
@@ -26,9 +26,9 @@ import java.util.NoSuchElementException;
 import javax.jcr.ItemNotFoundException;
 import javax.jcr.NodeIterator;
 import javax.jcr.RepositoryException;
-import javax.jcr.query.QueryResult;
 import javax.jcr.query.RowIterator;
 
+import org.apache.jackrabbit.api.query.JackrabbitQueryResult;
 import org.apache.jackrabbit.core.session.SessionContext;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.commons.query.qom.ColumnImpl;
@@ -38,7 +38,7 @@ import org.slf4j.LoggerFactory;
 /**
  * Implements the <code>QueryResult</code> interface.
  */
-public abstract class QueryResultImpl implements QueryResult {
+public abstract class QueryResultImpl implements JackrabbitQueryResult {
 
     /**
      * The logger instance for this class
@@ -73,15 +73,18 @@ public abstract class QueryResultImpl implements QueryResult {
     /**
      * The result nodes including their score. This list is populated on a lazy
      * basis while a client iterates through the results.
-     * <p/>
+     * <p>
      * The exact type is: <code>List<ScoreNode[]></code>
      */
     private final List<ScoreNode[]> resultNodes = new ArrayList<ScoreNode[]>();
 
     /**
-     * This is the raw number of results that matched the query. This number
-     * also includes matches which will not be returned due to access
-     * restrictions. This value is set whenever hits are obtained.
+     * This is the raw number of results that matched the query, ignoring limit and offset. Only set when accurate.
+     */
+    private int totalResults = -1;
+
+    /**
+     * This is the number of results that matched the query, with limit and offset. Only set when accurate.
      */
     private int numResults = -1;
 
@@ -116,6 +119,8 @@ public abstract class QueryResultImpl implements QueryResult {
      * The maximum size of this result if limit >= 0
      */
     private final long limit;
+    
+    private final boolean sizeEstimate;
 
     /**
      * Creates a new query result. The concrete sub class is responsible for
@@ -143,6 +148,7 @@ public abstract class QueryResultImpl implements QueryResult {
             ColumnImpl[] columns, boolean documentOrder,
             long offset, long limit) throws RepositoryException {
         this.index = index;
+        this.sizeEstimate = index.getSizeEstimate();
         this.sessionContext = sessionContext;
         this.queryImpl = queryImpl;
         this.spellSuggestion = spellSuggestion;
@@ -254,6 +260,14 @@ public abstract class QueryResultImpl implements QueryResult {
         if (log.isDebugEnabled()) {
             log.debug("getResults({}) limit={}", size, limit);
         }
+        
+        if (!sizeEstimate) {
+            // quick check
+            // if numResults is set, all relevant results have been fetched
+            if (numResults != -1) {
+                return;
+            }
+        }
 
         long maxResultSize = size;
 
@@ -279,9 +293,14 @@ public abstract class QueryResultImpl implements QueryResult {
             // set selector names
             selectorNames = result.getSelectorNames();
 
+            List<ScoreNode[]> offsetNodes = new ArrayList<ScoreNode[]>();
             if (resultNodes.isEmpty() && offset > 0) {
                 // collect result offset into dummy list
-                collectScoreNodes(result, new ArrayList<ScoreNode[]>(), offset);
+                if (sizeEstimate) {
+                    collectScoreNodes(result, new ArrayList<ScoreNode[]>(), offset);                    
+                } else {
+                    collectScoreNodes(result, offsetNodes, offset);
+                }
             } else {
                 int start = resultNodes.size() + invalid + (int) offset;
                 result.skip(start);
@@ -293,11 +312,31 @@ public abstract class QueryResultImpl implements QueryResult {
             log.debug("retrieved ScoreNodes in {} ms ({})",
                     System.currentTimeMillis() - time, r3 - r2);
 
-            // update numResults
-            numResults = result.getSize();
+            if (sizeEstimate) {
+                // update numResults
+                numResults = result.getSize();                
+            } else {
+                // update numResults if all results have been fetched 
+                // if resultNodes.getSize() is strictly smaller than maxResultSize, it means that all results have been fetched
+                int resultSize = resultNodes.size();
+                if (resultSize < maxResultSize) {
+                    if (resultNodes.isEmpty()) {
+                        // if there's no result nodes, the actual totalResults if smaller or equals than the offset
+                        totalResults = offsetNodes.size();
+                        numResults = 0;
+                    }
+                    else {
+                        totalResults = resultSize + (int) offset;
+                        numResults = resultSize;
+                    }
+                }
+                else if (resultSize == limit) {
+                    // if there's "limit" results, we can't know the total size (which may be greater), but the result size is the limit
+                    numResults = (int) limit;
+                }
+            }
         } catch (IOException e) {
-            log.error("Exception while executing query: ", e);
-            // todo throw?
+            throw new RepositoryException(e);
         } finally {
             if (result != null) {
                 try {
@@ -348,7 +387,7 @@ public abstract class QueryResultImpl implements QueryResult {
      * @throws RepositoryException if an error occurs while checking access
      *                             rights.
      */
-    private boolean isAccessGranted(ScoreNode[] nodes)
+    protected boolean isAccessGranted(ScoreNode[] nodes)
             throws RepositoryException {
         for (ScoreNode node : nodes) {
             try {
@@ -365,18 +404,25 @@ public abstract class QueryResultImpl implements QueryResult {
 
     /**
      * Returns the total number of hits. This is the number of results you
-     * will get get if you don't set any limit or offset. Keep in mind that this
-     * number may get smaller if nodes are found in the result set which the
-     * current session has no permission to access. This method may return
+     * will get get if you don't set any limit or offset. This method may return
      * <code>-1</code> if the total size is unknown.
+     * <p>
+     * If the "sizeEstimate" options is enabled:
+     * Keep in mind that this number may get smaller if nodes are found in
+     * the result set which the current session has no permission to access.
+     * This might be a security problem.
      *
      * @return the total number of hits.
      */
     public int getTotalSize() {
-        if (numResults == -1) {
-            return -1;
+        if (sizeEstimate) {
+            if (numResults == -1) {
+                return -1;
+            } else {
+                return numResults - invalid;
+            }
         } else {
-            return numResults - invalid;
+            return totalResults;
         }
     }
 
@@ -429,19 +475,24 @@ public abstract class QueryResultImpl implements QueryResult {
         /**
          * {@inheritDoc}
          * <p/>
+         * If the "sizeEstimate" options is enabled:
          * This value may shrink when the query result encounters non-existing
          * nodes or the session does not have access to a node.
          */
         public long getSize() {
-            int total = getTotalSize();
-            if (total == -1) {
-                return -1;
-            }
-            long size = offset > total ? 0 : total - offset;
-            if (limit >= 0 && size > limit) {
-                return limit;
+            if (sizeEstimate) {
+                int total = getTotalSize();
+                if (total == -1) {
+                    return -1;
+                }
+                long size = offset > total ? 0 : total - offset;
+                if (limit >= 0 && size > limit) {
+                    return limit;
+                } else {
+                    return size;
+                }                
             } else {
-                return size;
+                return numResults;
             }
         }
 
@@ -496,9 +547,16 @@ public abstract class QueryResultImpl implements QueryResult {
             while (next == null) {
                 if (nextPos >= resultNodes.size()) {
                     // quick check if there are more results at all
-                    // this check is only possible if we have numResults
-                    if (numResults != -1 && (nextPos + invalid) >= numResults) {
-                        break;
+                    if (sizeEstimate) {
+                        // this check is only possible if we have numResults
+                        if (numResults != -1 && (nextPos + invalid) >= numResults) {
+                            break;
+                        }
+                    } else {
+                        // if numResults is set, all relevant results have been fetched
+                        if (numResults != -1) {
+                            break;
+                        }
                     }
 
                     // fetch more results
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/RangeQuery.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/RangeQuery.java
index b2fd134..c3ebc55 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/RangeQuery.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/RangeQuery.java
@@ -22,6 +22,7 @@ import org.apache.lucene.index.TermDocs;
 import org.apache.lucene.index.TermEnum;
 import org.apache.lucene.search.BooleanQuery;
 import org.apache.lucene.search.Explanation;
+import org.apache.lucene.search.MultiTermQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.Scorer;
 import org.apache.lucene.search.Searcher;
@@ -148,8 +149,11 @@ public class RangeQuery extends Query implements Transformable {
      */
     public Query rewrite(IndexReader reader) throws IOException {
         if (transform == TRANSFORM_NONE) {
-            Query stdRangeQueryImpl
-                    = new TermRangeQuery(lowerTerm.field(), lowerTerm.text(), upperTerm.text(), inclusive, inclusive);
+            TermRangeQuery stdRangeQueryImpl = new TermRangeQuery(
+                    lowerTerm.field(), lowerTerm.text(), upperTerm.text(),
+                    inclusive, inclusive);
+            stdRangeQueryImpl
+                    .setRewriteMethod(MultiTermQuery.CONSTANT_SCORE_BOOLEAN_QUERY_REWRITE);
             try {
                 stdRangeQuery = stdRangeQueryImpl.rewrite(reader);
                 return stdRangeQuery;
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ReadOnlyIndexReader.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ReadOnlyIndexReader.java
index 2648c1e..116ca89 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ReadOnlyIndexReader.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/ReadOnlyIndexReader.java
@@ -16,12 +16,16 @@
  */
 package org.apache.jackrabbit.core.query.lucene;
 
+import org.apache.lucene.index.FieldInfos;
+import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.TermDocs;
 import org.apache.lucene.index.TermPositions;
+import org.apache.lucene.util.ReaderUtil;
 
 import java.io.IOException;
 import java.util.BitSet;
+import java.util.Collection;
 import java.util.Map;
 
 /**
@@ -36,7 +40,7 @@ class ReadOnlyIndexReader extends RefCountingIndexReader {
      * The deleted documents as initially read from the IndexReader passed
      * in the constructor of this class.
      */
-    private final BitSet deleted;
+    private BitSet deleted;
 
     /**
      * The version of the index reader from where the deleted BitSet was
@@ -85,7 +89,7 @@ class ReadOnlyIndexReader extends RefCountingIndexReader {
      * Updates the deleted documents in this index reader. When this method
      * returns this index reader will have the same documents marked as deleted
      * as the passed <code>reader</code>.
-     * <p/>
+     * <p>
      * This method is not thread-safe! Make sure no other thread is concurrently
      * using this reader at the same time.
      *
@@ -93,10 +97,14 @@ class ReadOnlyIndexReader extends RefCountingIndexReader {
      *               info.
      */
     void updateDeletedDocs(CommittableIndexReader reader) {
-        int maxDoc = reader.maxDoc();
-        for (int i = 0; i < maxDoc; i++) {
-            if (reader.isDeleted(i)) {
-                deleted.set(i);
+        Collection<Integer> deletes = reader.getDeletedSince(deletedDocsVersion);
+        if (deletes == null) {
+            // full update needed
+            this.deleted = reader.getDeletedDocs();
+        } else {
+            // incremental update
+            for (Integer d : deletes) {
+                deleted.set(d);
             }
         }
         deletedDocsVersion = reader.getModificationCount();
@@ -171,7 +179,12 @@ class ReadOnlyIndexReader extends RefCountingIndexReader {
      * @exception UnsupportedOperationException always
      */
     @Override
-    protected void doCommit(Map<String,String> commitUserData) throws IOException { 
+    protected void doCommit(Map<String, String> commitUserData) throws IOException {
+        if (!hasChanges) {
+            // change in behavior: IndexReader does not check for hasChanges
+            // before calling doCommit();
+            return;
+        }
         throw new UnsupportedOperationException("IndexReader is read-only");
     }
 
@@ -217,6 +230,16 @@ class ReadOnlyIndexReader extends RefCountingIndexReader {
         return new FilteredTermPositions(super.termPositions());
     }
 
+    @Override
+    public String toString() {
+      final StringBuilder buffer = new StringBuilder("ReadOnlyIndexReader(");
+      buffer.append(in);
+      buffer.append(',');
+      buffer.append(deletedDocsVersion);
+      buffer.append(')');
+      return buffer.toString();
+    }
+
     //----------------------< FilteredTermDocs >--------------------------------
 
     /**
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/Recovery.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/Recovery.java
index 327dd6b..8047298 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/Recovery.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/Recovery.java
@@ -63,7 +63,7 @@ class Recovery {
     /**
      * Runs a recovery on <code>index</code> if <code>redoLog</code> contains
      * log entries.
-     * <p/>
+     * <p>
      * If recovery succeeds the <code>index</code> is flushed and the redo log
      * is cleared. That is, the <code>index</code> is stable.<br/>
      * If recovery fails an IOException is thrown, and the redo log will not
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/RefCountingIndexReader.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/RefCountingIndexReader.java
index c68c48f..91614f2 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/RefCountingIndexReader.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/RefCountingIndexReader.java
@@ -16,8 +16,10 @@
  */
 package org.apache.jackrabbit.core.query.lucene;
 
+import org.apache.lucene.index.FieldInfos;
 import org.apache.lucene.index.FilterIndexReader;
 import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.util.ReaderUtil;
 
 import java.io.IOException;
 
@@ -48,7 +50,7 @@ public class RefCountingIndexReader
     /**
      * @return the current reference count value.
      */
-    public synchronized int getRefCount() {
+    public synchronized int getRefCountJr() {
         return refCount;
     }
 
@@ -65,6 +67,16 @@ public class RefCountingIndexReader
 
     //-----------------------< FilterIndexReader >--------------------------
 
+    @Override
+    public IndexReader[] getSequentialSubReaders() {
+        return null;
+    }
+
+    @Override
+    public FieldInfos getFieldInfos() {
+        return ReaderUtil.getMergedFieldInfos(in);
+    }
+
     protected void doClose() throws IOException {
         Util.closeOrRelease(in);
     }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/RowIteratorImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/RowIteratorImpl.java
index 7676712..3428b78 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/RowIteratorImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/RowIteratorImpl.java
@@ -208,7 +208,7 @@ class RowIteratorImpl implements RowIterator {
      * Returns the current position within this iterator. The number
      * returned is the 0-based index of the next <code>Row</code> in the iterator,
      * i.e. the one that will be returned on the subsequent <code>next</code> call.
-     * <p/>
+     * <p>
      * Note that this method does not check if there is a next element,
      * i.e. an empty iterator will always return 0.
      *
@@ -310,7 +310,7 @@ class RowIteratorImpl implements RowIterator {
 
         /**
          * Returns the value of the indicated  column in this <code>Row</code>.
-         * <p/>
+         * <p>
          * If <code>columnbName</code> is not among the column names of the
          * query result table, an <code>ItemNotFoundException</code> is thrown.
          *
@@ -377,7 +377,7 @@ class RowIteratorImpl implements RowIterator {
 
         /**
          * Returns the <code>Node</code> corresponding to this <code>Row</code>.
-         * <p/>
+         * <p>
          * A <code>RepositoryException</code> is thrown if this <code>Row</code>
          * contains values from more than one node. This will be the case when more
          * than one selector is included among the columns specified for the query.
@@ -450,13 +450,13 @@ class RowIteratorImpl implements RowIterator {
         /**
          * Returns the full text search score for this row associated with the
          * default selector. This corresponds to the score of a particular node.
-         * <p/>
+         * <p>
          * If no <code>FullTextSearchScore</code> AQM object is associated with the
          * default selector this method will still return a value. However, in that
          * case the returned value may not be meaningful or may simply reflect the
          * minimum possible relevance level (for example, in some systems this might
          * be a score of 0).
-         * <p/>
+         * <p>
          * Note, in JCR-SQL2 a <code>FullTextSearchScore</code> AQM object is represented
          * by a <code>SCORE()</code> function. In JCR-JQOM it is represented by a
          * Java object of type <code>javax.jcr.query.qom.FullTextSearchScore</code>.
@@ -475,13 +475,13 @@ class RowIteratorImpl implements RowIterator {
         /**
          * Returns the full text search score for this row associated with the
          * specified selector. This corresponds to the score of a particular node.
-         * <p/>
+         * <p>
          * If no <code>FullTextSearchScore</code> AQM object is associated with the
          * selector <code>selectorName</code> this method will still return a value.
          * However, in that case the returned value may not be meaningful or may
          * simply reflect the minimum possible relevance level (for example, in some
          * systems this might be a score of 0).
-         * <p/>
+         * <p>
          * Note, in JCR-SQL2 a <code>FullTextSearchScore</code> AQM object is represented
          * by a <code>SCORE()</code> function. In JCR-JQOM it is represented by a
          * Java object of type <code>javax.jcr.query.qom.FullTextSearchScore</code>.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java
index 140c9ba..a9ba357 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java
@@ -33,6 +33,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 import javax.jcr.query.InvalidQueryException;
 import javax.xml.parsers.DocumentBuilder;
@@ -82,6 +83,7 @@ import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
 import org.apache.jackrabbit.spi.commons.query.DefaultQueryNodeFactory;
 import org.apache.jackrabbit.spi.commons.query.qom.OrderingImpl;
 import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.LimitTokenCountAnalyzer;
 import org.apache.lucene.analysis.TokenStream;
 import org.apache.lucene.analysis.tokenattributes.PayloadAttribute;
 import org.apache.lucene.analysis.tokenattributes.TermAttribute;
@@ -99,6 +101,7 @@ import org.apache.lucene.search.Similarity;
 import org.apache.lucene.search.Sort;
 import org.apache.lucene.search.SortField;
 import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.util.Version;
 import org.apache.tika.config.TikaConfig;
 import org.apache.tika.fork.ForkParser;
 import org.apache.tika.parser.AutoDetectParser;
@@ -252,7 +255,7 @@ public class SearchIndex extends AbstractQueryHandler {
 
     /**
      * The location of the search index.
-     * <p/>
+     * <p>
      * Note: This is a <b>mandatory</b> parameter!
      */
     private String path;
@@ -330,7 +333,7 @@ public class SearchIndex extends AbstractQueryHandler {
     /**
      * Flag indicating whether document order is enabled as the default
      * ordering.
-     * <p/>
+     * <p>
      * Default value is: <code>false</code>.
      */
     private boolean documentOrder = false;
@@ -339,7 +342,7 @@ public class SearchIndex extends AbstractQueryHandler {
      * If set <code>true</code> the index is checked for consistency on startup.
      * If <code>false</code> a consistency check is only performed when there
      * are entries in the redo log on startup.
-     * <p/>
+     * <p>
      * Default value is: <code>false</code>.
      */
     private boolean forceConsistencyCheck = false;
@@ -349,7 +352,7 @@ public class SearchIndex extends AbstractQueryHandler {
      * on the {@link #forceConsistencyCheck} parameter. If set to
      * <code>false</code>, no consistency check is performed, even if the redo
      * log had been applied on startup.
-     * <p/>
+     * <p>
      * Default value is: <code>false</code>.
      */
     private boolean consistencyCheckEnabled = false;
@@ -357,21 +360,21 @@ public class SearchIndex extends AbstractQueryHandler {
     /**
      * If set <code>true</code> errors detected by the consistency check are
      * repaired. If <code>false</code> the errors are only reported in the log.
-     * <p/>
+     * <p>
      * Default value is: <code>true</code>.
      */
     private boolean autoRepair = true;
 
     /**
      * The id resolver cache size.
-     * <p/>
+     * <p>
      * Default value is: <code>1000</code>.
      */
     private int cacheSize = 1000;
 
     /**
      * The number of documents that are pre fetched when a query is executed.
-     * <p/>
+     * <p>
      * Default value is: {@link Integer#MAX_VALUE}.
      */
     private int resultFetchSize = Integer.MAX_VALUE;
@@ -379,10 +382,18 @@ public class SearchIndex extends AbstractQueryHandler {
     /**
      * If set to <code>true</code> the fulltext field is stored and and a term
      * vector is created with offset information.
-     * <p/>
+     * <p>
      * Default value is: <code>false</code>.
      */
     private boolean supportHighlighting = false;
+    
+    /**
+     * If enabled, NodeIterator.getSize() may report a larger value than the
+     * actual result. This value may shrink when the query result encounters
+     * non-existing nodes or the session does not have access to a node. This
+     * might be a security problem.
+     */
+    private boolean sizeEstimate = false;
 
     /**
      * The excerpt provider class. Implements {@link ExcerptProvider}.
@@ -470,6 +481,14 @@ public class SearchIndex extends AbstractQueryHandler {
     private DirectoryManager directoryManager;
 
     /**
+     * Flag that indicates whether the {@link DirectoryManager} should
+     * use the <code>SimpleFSDirectory</code> instead of letting Lucene
+     * automatically pick an implementation based on the platform we are
+     * running on. Note: see JCR-3818 for a discussion on the trade-off.
+     */
+    private boolean useSimpleFSDirectory = true;
+
+    /**
      * The termInfosIndexDivisor.
      */
     private int termInfosIndexDivisor = DEFAULT_TERM_INFOS_INDEX_DIVISOR;
@@ -748,6 +767,32 @@ public class SearchIndex extends AbstractQueryHandler {
         return ids;
     }
 
+    List<Document> getNodeDocuments(NodeId id) throws RepositoryException, IOException {
+        final List<Integer> docIds = new ArrayList<Integer>(1);
+        final List<Document> docs = new ArrayList<Document>();
+        final IndexReader reader = getIndexReader();
+        try {
+            IndexSearcher searcher = new IndexSearcher(reader);
+            try {
+                Query q = new TermQuery(new Term(FieldNames.UUID, id.toString()));
+                searcher.search(q, new AbstractHitCollector() {
+                    @Override
+                    protected void collect(final int doc, final float score) {
+                        docIds.add(doc);
+                    }
+                });
+                for (Integer docId : docIds) {
+                    docs.add(reader.document(docId, FieldSelectors.UUID_AND_PARENT));
+                }
+            } finally {
+                searcher.close();
+            }
+        } finally {
+            Util.closeOrRelease(reader);
+        }
+        return docs;
+    }
+
     /**
      * This method returns the QueryNodeFactory used to parse Queries. This method
      * may be overridden to provide a customized QueryNodeFactory
@@ -897,7 +942,7 @@ public class SearchIndex extends AbstractQueryHandler {
      * @return the analyzer in use for indexing.
      */
     public Analyzer getTextAnalyzer() {
-        return analyzer;
+        return new LimitTokenCountAnalyzer(analyzer, getMaxFieldLength());
     }
 
     /**
@@ -1156,6 +1201,8 @@ public class SearchIndex extends AbstractQueryHandler {
                     sortFields.add(new SortField(orderProps[i].getString(), new UpperCaseSortComparator(scs), !orderSpecs[i]));
                 } else if ("lower-case".equals(orderFuncs[i])) {
                     sortFields.add(new SortField(orderProps[i].getString(), new LowerCaseSortComparator(scs), !orderSpecs[i]));
+                } else if ("normalize".equals(orderFuncs[i])) {
+                    sortFields.add(new SortField(orderProps[i].getString(), new NormalizeSortComparator(scs), !orderSpecs[i]));
                 } else {
                     sortFields.add(new SortField(orderProps[i].getString(), scs, !orderSpecs[i]));
                 }
@@ -1514,14 +1561,18 @@ public class SearchIndex extends AbstractQueryHandler {
                                     String value = new String(termAttribute.termBuffer(), 0, termAttribute.termLength());
                                     if (value.startsWith(namePrefix)) {
                                         // extract value
-                                        value = value.substring(namePrefix.length());
+                                        String rawValue = value.substring(namePrefix.length());
                                         // create new named value
                                         Path p = getRelativePath(state, propState);
                                         String path = getNamespaceMappings().translatePath(p);
-                                        value = FieldNames.createNamedValue(path, value);
+                                        value = FieldNames.createNamedValue(path, rawValue);
                                         termAttribute.setTermBuffer(value);
+                                        PropertyMetaData pdm = PropertyMetaData
+                                                .fromByteArray(payloadAttribute
+                                                        .getPayload().getData());
                                         doc.add(new Field(field.name(),
-                                                new SingletonTokenStream(value, (Payload) payloadAttribute.getPayload().clone())));
+                                                new SingletonTokenStream(value,
+                                                        pdm.getPropertyType())));
                                         doc.add(new Field(
                                                 FieldNames.AGGREGATED_NODE_UUID,
                                                 false,
@@ -1529,6 +1580,17 @@ public class SearchIndex extends AbstractQueryHandler {
                                                 Field.Store.NO,
                                                 Field.Index.NOT_ANALYZED_NO_NORMS,
                                                 Field.TermVector.NO));
+                                        if (pdm.getPropertyType() == PropertyType.STRING) {
+                                            // add to fulltext index
+                                            Field ft = new Field(
+                                                    FieldNames.FULLTEXT,
+                                                    false,
+                                                    rawValue,
+                                                    Field.Store.YES,
+                                                    Field.Index.ANALYZED_NO_NORMS,
+                                                    Field.TermVector.NO);
+                                            doc.add(ft);
+                                        }
                                     }
                                 }
                             } finally {
@@ -1590,7 +1652,7 @@ public class SearchIndex extends AbstractQueryHandler {
         if (!clean) {
             p = PATH_FACTORY.create(elements);
         }
-        return p;
+        return p.getNormalizedPath();
     }
 
     /**
@@ -1742,22 +1804,9 @@ public class SearchIndex extends AbstractQueryHandler {
          */
         private final CachingMultiIndexReader[] subReaders;
 
-        /**
-         * Doc number starts for each sub reader
-         */
-        private int[] starts;
-
         public CombinedIndexReader(CachingMultiIndexReader[] indexReaders) {
             super(indexReaders);
             this.subReaders = indexReaders;
-            this.starts = new int[subReaders.length + 1];
-
-            int maxDoc = 0;
-            for (int i = 0; i < subReaders.length; i++) {
-                starts[i] = maxDoc;
-                maxDoc += subReaders[i].maxDoc();
-            }
-            starts[subReaders.length] = maxDoc;
         }
 
         /**
@@ -1790,36 +1839,6 @@ public class SearchIndex extends AbstractQueryHandler {
             }
         }
 
-        //---------------------------< internal >-------------------------------
-
-        /**
-         * Returns the reader index for document <code>n</code>.
-         * Implementation copied from lucene MultiReader class.
-         *
-         * @param n document number.
-         * @return the reader index.
-         */
-        private int readerIndex(int n) {
-            int lo = 0;                                      // search starts array
-            int hi = subReaders.length - 1;                  // for first element less
-
-            while (hi >= lo) {
-                int mid = (lo + hi) >> 1;
-                int midValue = starts[mid];
-                if (n < midValue) {
-                    hi = mid - 1;
-                } else if (n > midValue) {
-                    lo = mid + 1;
-                } else {                                      // found a match
-                    while (mid + 1 < subReaders.length && starts[mid + 1] == midValue) {
-                        mid++;                                  // scan to last match
-                    }
-                    return mid;
-                }
-            }
-            return hi;
-        }
-
         public boolean equals(Object obj) {
             if (obj instanceof CombinedIndexReader) {
                 CombinedIndexReader other = (CombinedIndexReader) obj;
@@ -1867,11 +1886,12 @@ public class SearchIndex extends AbstractQueryHandler {
     //--------------------------< properties >----------------------------------
 
     /**
-     * Sets the analyzer in use for indexing. The given analyzer class name
-     * must satisfy the following conditions:
+     * Sets the default analyzer in use for indexing. The given analyzer
+     * class name must satisfy the following conditions:
      * <ul>
      *   <li>the class must exist in the class path</li>
-     *   <li>the class must have a public default constructor</li>
+     *   <li>the class must have a public default constructor, or
+     *       a constructor that takes a Lucene {@link Version} argument</li>
      *   <li>the class must be a Lucene Analyzer</li>
      * </ul>
      * <p>
@@ -1886,21 +1906,16 @@ public class SearchIndex extends AbstractQueryHandler {
      * @param analyzerClassName the analyzer class name
      */
     public void setAnalyzer(String analyzerClassName) {
-        try {
-            Class<?> analyzerClass = Class.forName(analyzerClassName);
-            analyzer.setDefaultAnalyzer((Analyzer) analyzerClass.newInstance());
-        } catch (Exception e) {
-            log.warn("Invalid Analyzer class: " + analyzerClassName, e);
-        }
+        analyzer.setDefaultAnalyzerClass(analyzerClassName);
     }
 
     /**
-     * Returns the class name of the analyzer that is currently in use.
+     * Returns the class name of the default analyzer that is currently in use.
      *
      * @return class name of analyzer in use.
      */
     public String getAnalyzer() {
-        return analyzer.getClass().getName();
+        return analyzer.getDefaultAnalyzerClass();
     }
 
     /**
@@ -2171,6 +2186,30 @@ public class SearchIndex extends AbstractQueryHandler {
     public long getExtractorTimeout() {
         return extractorTimeout;
     }
+    
+    /**
+     * If enabled, NodeIterator.getSize() may report a larger value than the
+     * actual result. This value may shrink when the query result encounters
+     * non-existing nodes or the session does not have access to a node. This
+     * might be a security problem.
+     * 
+     * @param b <code>true</code> to enable
+     */
+    public void setSizeEstimate(boolean b) {
+        if (b) {
+            log.info("Size estimation is enabled");
+        }
+        this.sizeEstimate = b;
+    }
+    
+    /**
+     * Get the size estimate setting.
+     * 
+     * @return the setting
+     */
+    public boolean getSizeEstimate() {
+        return sizeEstimate;
+    }
 
     /**
      * If set to <code>true</code> additional information is stored in the index
@@ -2424,6 +2463,26 @@ public class SearchIndex extends AbstractQueryHandler {
     }
 
     /**
+     * If set <code>true</code> will indicate to the {@link DirectoryManager}
+     * to use the <code>SimpleFSDirectory</code>.
+     *
+     * @param useSimpleFSDirectory whether to use <code>SimpleFSDirectory</code>
+     *                             or automatically pick an implementation based
+     *                             on the current platform.
+     */
+    public void setUseSimpleFSDirectory(boolean useSimpleFSDirectory) {
+        this.useSimpleFSDirectory = useSimpleFSDirectory;
+    }
+
+    /**
+     * @return <code>true</code> if the {@link DirectoryManager} should use
+     * the <code>SimpleFSDirectory</code>.
+     */
+    public boolean isUseSimpleFSDirectory() {
+        return useSimpleFSDirectory;
+    }
+
+    /**
      * @return the current value for termInfosIndexDivisor.
      */
     public int getTermInfosIndexDivisor() {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldCache.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldCache.java
index 83dcdd5..e22cbac 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldCache.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SharedFieldCache.java
@@ -207,7 +207,7 @@ public class SharedFieldCache {
      * Creates a <code>ValueIndex</code> for a <code>field</code> and a term
      * <code>prefix</code>. The term prefix acts as the property name for the
      * shared <code>field</code>.
-     * <p/>
+     * <p>
      * This method is an adapted version of: <code>FieldCacheImpl.getStringIndex()</code>
      *
      * @param reader     the <code>IndexReader</code>.
@@ -228,7 +228,9 @@ public class SharedFieldCache {
         ValueIndex ret = lookup(reader, field, prefix);
         if (ret == null) {
             final int maxDocs = reader.maxDoc();
-            ComparableArray[] retArray = new ComparableArray[maxDocs];
+            Comparable<?>[] retArray = new Comparable<?>[maxDocs];
+            Map<Integer, Integer> positions = new HashMap<Integer, Integer>();
+            boolean usingSimpleComparable = true;
             int setValues = 0;
             if (maxDocs > 0) {
                 IndexFormatVersion version = IndexFormatVersion.getVersion(reader);
@@ -268,11 +270,34 @@ public class SharedFieldCache {
                             setValues++;
                             Comparable<?> v = getValue(value, type);
                             int doc = termDocs.doc();
-                            ComparableArray ca = retArray[doc];
+                            Comparable<?> ca = retArray[doc];
                             if (ca == null) {
-                                retArray[doc] = new ComparableArray(v, termPosition);
+                                if (usingSimpleComparable) {
+                                    // put simple value on the queue
+                                    positions.put(doc, termPosition);
+                                    retArray[doc] = v;
+                                } else {
+                                    retArray[doc] = new ComparableArray(v,
+                                            termPosition);
+                                }
                             } else {
-                                retArray[doc] = ca.insert(v, termPosition);
+                                if (ca instanceof ComparableArray) {
+                                    ((ComparableArray) ca).insert(v,
+                                            termPosition);
+                                } else {
+                                    // transform all of the existing values from
+                                    // Comparable to ComparableArray
+                                    for (int pos : positions.keySet()) {
+                                        retArray[pos] = new ComparableArray(
+                                                retArray[pos],
+                                                positions.get(pos));
+                                    }
+                                    positions = null;
+                                    usingSimpleComparable = false;
+                                    ComparableArray caNew = (ComparableArray) retArray[doc];
+                                    retArray[doc] = caNew.insert(v,
+                                            termPosition);
+                                }
                             }
                         }
                     } while (termEnum.next());
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/Util.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/Util.java
index fc187db..e8fe0bf 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/Util.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/Util.java
@@ -257,6 +257,9 @@ public class Util {
      * consistent
      */
     public static int compare(Comparable<?>[] c1, Comparable<?>[] c2) {
+        if(c1 == null && c2 == null){
+            return 0;
+        }
         if (c1 == null) {
             return -1;
         }
@@ -280,6 +283,9 @@ public class Util {
      * consistent
      */
     public static int compare(Value[] a, Value[] b) throws RepositoryException {
+        if(a == null && b == null){
+            return 0;
+        }
         if (a == null) {
             return -1;
         }
@@ -299,7 +305,7 @@ public class Util {
      * Compares the two values. If the values have differing types, then an
      * attempt is made to convert the second value into the type of the first
      * value.
-     * <p/>
+     * <p>
      * Comparison of binary values is not supported.
      * 
      * @param v1
@@ -431,7 +437,7 @@ public class Util {
             try {
                 return value.getLength();
             } catch (RepositoryException e) {
-                log.warn("Unable to determine length of value.", e.getMessage());
+                log.warn("Unable to determine length of value. {}", e.getMessage());
                 return -1;
             }
         }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardNameQuery.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardNameQuery.java
index 6b5a73a..cb6ba64 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardNameQuery.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardNameQuery.java
@@ -24,7 +24,7 @@ import org.slf4j.LoggerFactory;
 
 /**
  * Implements a wildcard query on the name field.
- * <p/>
+ * <p>
  * Wildcards are:
  * <ul>
  * <li><code>%</code> : matches zero or more characters</li>
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardQuery.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardQuery.java
index 7b592f9..1c1dfdb 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardQuery.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/WildcardQuery.java
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.core.query.lucene;
 
+import org.apache.jackrabbit.core.query.lucene.WildcardTermEnum.TermValueFactory;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.TermEnum;
@@ -42,7 +43,7 @@ import java.util.Set;
 /**
  * Implements a wildcard query on a lucene field with an embedded property name
  * and a pattern.
- * <p/>
+ * <p>
  * Wildcards are:
  * <ul>
  * <li><code>%</code> : matches zero or more characters</li>
@@ -65,7 +66,7 @@ public class WildcardQuery extends Query implements Transformable {
     /**
      * Creates a term value for a given string.
      */
-    private final WildcardTermEnum.TermValueFactory tvf;
+    private final TermValueFactory tvf;
 
     /**
      * The wildcard pattern.
@@ -144,23 +145,9 @@ public class WildcardQuery extends Query implements Transformable {
      */
     @Override
     public Query rewrite(IndexReader reader) throws IOException {
-        Query stdWildcardQuery = new MultiTermQuery() {
-            protected FilteredTermEnum getEnum(IndexReader reader) throws IOException {
-                return new WildcardTermEnum(reader, field, tvf, pattern, transform);
-            }
-
-            /** Prints a user-readable version of this query. */
-            @Override
-            public String toString(String field) {
-                StringBuffer buffer = new StringBuffer();
-                buffer.append(field);
-                buffer.append(':');
-                buffer.append(ToStringUtils.boost(getBoost()));
-                return buffer.toString();
-            }
-        };
         try {
-            multiTermQuery = stdWildcardQuery.rewrite(reader);
+            multiTermQuery = new StdWildcardQuery(field, tvf, pattern,
+                    transform).rewrite(reader);
             return multiTermQuery;
         } catch (BooleanQuery.TooManyClauses e) {
             // MultiTermQuery not possible
@@ -198,6 +185,39 @@ public class WildcardQuery extends Query implements Transformable {
         }
     }
 
+    private static class StdWildcardQuery extends MultiTermQuery {
+
+        private final String field;
+        private final TermValueFactory tvf;
+        private final String pattern;
+        private final int transform;
+
+        public StdWildcardQuery(String field, TermValueFactory tvf,
+                String pattern, int transform) {
+            this.field = field;
+            this.tvf = tvf;
+            this.pattern = pattern;
+            this.transform = transform;
+            setRewriteMethod(CONSTANT_SCORE_BOOLEAN_QUERY_REWRITE);
+        }
+
+        @Override
+        protected FilteredTermEnum getEnum(IndexReader reader)
+                throws IOException {
+            return new WildcardTermEnum(reader, field, tvf, pattern, transform);
+        }
+
+        /** Prints a user-readable version of this query. */
+        @Override
+        public String toString(String field) {
+            StringBuffer buffer = new StringBuffer();
+            buffer.append(field);
+            buffer.append(':');
+            buffer.append(ToStringUtils.boost(getBoost()));
+            return buffer.toString();
+        }
+    }
+
     /**
      * The <code>Weight</code> implementation for this <code>WildcardQuery</code>.
      */
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/directory/FSDirectoryManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/directory/FSDirectoryManager.java
index 15c7eba..d2cbe0d 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/directory/FSDirectoryManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/directory/FSDirectoryManager.java
@@ -25,6 +25,7 @@ import org.apache.lucene.store.IndexOutput;
 import org.apache.lucene.store.Lock;
 import org.apache.lucene.store.LockFactory;
 import org.apache.lucene.store.NativeFSLockFactory;
+import org.apache.lucene.store.SimpleFSDirectory;
 
 import java.io.File;
 import java.io.FileFilter;
@@ -41,11 +42,14 @@ public class FSDirectoryManager implements DirectoryManager {
      */
     private File baseDir;
 
+    private boolean useSimpleFSDirectory;
+
     /**
      * {@inheritDoc}
      */
     public void init(SearchIndex handler) throws IOException {
         baseDir = new File(handler.getPath());
+        useSimpleFSDirectory = handler.isUseSimpleFSDirectory();
     }
 
     /**
@@ -66,7 +70,7 @@ public class FSDirectoryManager implements DirectoryManager {
         } else {
             dir = new File(baseDir, name);
         }
-        return new FSDir(dir);
+        return new FSDir(dir, useSimpleFSDirectory);
     }
 
     /**
@@ -140,19 +144,23 @@ public class FSDirectoryManager implements DirectoryManager {
 
         private final FSDirectory directory;
 
-        public FSDir(File dir) throws IOException {
+        public FSDir(File dir, boolean simpleFS) throws IOException {
             if (!dir.mkdirs()) {
                 if (!dir.isDirectory()) {
                     throw new IOException("Unable to create directory: '" + dir + "'");
                 }
             }
-            directory = FSDirectory.open(dir,
-                    new NativeFSLockFactory(dir));
+            LockFactory lockFactory = new NativeFSLockFactory(dir);
+            if (simpleFS) {
+                directory = new SimpleFSDirectory(dir, lockFactory);
+            } else {
+                directory = FSDirectory.open(dir, lockFactory);
+            }
         }
 
         @Override
         public String[] listAll() throws IOException {
-            File[] files = directory.getFile().listFiles(FILTER);
+            File[] files = directory.getDirectory().listFiles(FILTER);
             if (files == null) {
                 return null;
             }
@@ -196,7 +204,7 @@ public class FSDirectoryManager implements DirectoryManager {
         @Override
         public IndexInput openInput(String name) throws IOException {
             IndexInput in = directory.openInput(name);
-            return new IndexInputLogWrapper(in);
+            return new IndexInputLogWrapper(name, in);
         }
 
         @Override
@@ -208,7 +216,7 @@ public class FSDirectoryManager implements DirectoryManager {
         public IndexInput openInput(String name, int bufferSize)
                 throws IOException {
             IndexInput in = directory.openInput(name, bufferSize);
-            return new IndexInputLogWrapper(in);
+            return new IndexInputLogWrapper(name, in);
         }
 
         @Override
@@ -222,7 +230,7 @@ public class FSDirectoryManager implements DirectoryManager {
         }
 
         @Override
-        public void setLockFactory(LockFactory lockFactory) {
+        public void setLockFactory(LockFactory lockFactory) throws IOException {
             directory.setLockFactory(lockFactory);
         }
 
@@ -249,7 +257,8 @@ public class FSDirectoryManager implements DirectoryManager {
 
         private IndexInput in;
 
-        IndexInputLogWrapper(IndexInput in) {
+        IndexInputLogWrapper(String name, IndexInput in) {
+            super(name);
             this.in = in;
         }
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/directory/IndexInputStream.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/directory/IndexInputStream.java
index 3e2df8c..128e022 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/directory/IndexInputStream.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/directory/IndexInputStream.java
@@ -81,7 +81,7 @@ public class IndexInputStream extends InputStream {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Closes the underlying index input.
      */
     public void close() throws IOException {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/directory/IndexOutputStream.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/directory/IndexOutputStream.java
index 665a631..917bc42 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/directory/IndexOutputStream.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/directory/IndexOutputStream.java
@@ -64,7 +64,7 @@ public class IndexOutputStream extends OutputStream {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Flushes the underlying index output.
      */
     public void flush() throws IOException {
@@ -73,7 +73,7 @@ public class IndexOutputStream extends OutputStream {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Closes the underlying index output.
      */
     public void close() throws IOException {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/AbstractHitCollector.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/AbstractHitCollector.java
index 8e2ab54..d8e1d3b 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/AbstractHitCollector.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/hits/AbstractHitCollector.java
@@ -49,7 +49,7 @@ public abstract class AbstractHitCollector extends Collector {
      * Called once for every document matching a query, with the re-based document
      * number and its computed score.
      * @param doc the re-based document number.
-     * @param doc the document's score.
+     * @param score the document's score.
      */
     protected abstract void collect(int doc, float score);
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AbstractRow.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AbstractRow.java
index 9e0a0e1..5c9bd5f 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AbstractRow.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AbstractRow.java
@@ -42,8 +42,9 @@ abstract class AbstractRow implements Row {
 
     public Value[] getValues() throws RepositoryException {
         Value[] values = new Value[columns.size()];
-        for (Operand operand : columns.values()) {
-            values = evaluator.getValues(operand, this);
+        int i = 0;
+        for (String columnName : columns.keySet()) {
+            values[i++] = getValue(columnName);
         }
         return values;
     }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AncestorNodeJoin.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AncestorNodeJoin.java
index 99e0fee..d023383 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AncestorNodeJoin.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AncestorNodeJoin.java
@@ -88,7 +88,7 @@ public class AncestorNodeJoin extends AbstractCondition {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * The outer query hits loop contains the ancestor score nodes.
      */
     public ScoreNode[][] getMatchingScoreNodes(ScoreNode ancestor)
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AncestorPathNodeJoin.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AncestorPathNodeJoin.java
index 828889f..e9d379b 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AncestorPathNodeJoin.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/AncestorPathNodeJoin.java
@@ -79,7 +79,7 @@ public class AncestorPathNodeJoin extends AbstractCondition {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * The outer query hits loop contains the ancestor nodes.
      */
     public ScoreNode[][] getMatchingScoreNodes(ScoreNode ancestor)
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ChildNodeJoin.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ChildNodeJoin.java
index 92fece0..679b08a 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ChildNodeJoin.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/ChildNodeJoin.java
@@ -84,7 +84,7 @@ public class ChildNodeJoin extends AbstractCondition {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * The outer query hits loop contains the child nodes.
      */
     public ScoreNode[][] getMatchingScoreNodes(ScoreNode child) throws IOException {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantNodeJoin.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantNodeJoin.java
index b960e98..a682e0e 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantNodeJoin.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantNodeJoin.java
@@ -88,7 +88,7 @@ public class DescendantNodeJoin extends AbstractCondition {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * The outer query hits loop contains the descendant nodes.
      */
     public ScoreNode[][] getMatchingScoreNodes(ScoreNode descendant)
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantPathNodeJoin.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantPathNodeJoin.java
index e3b7163..d276510 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantPathNodeJoin.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/DescendantPathNodeJoin.java
@@ -77,7 +77,7 @@ public class DescendantPathNodeJoin extends AbstractCondition {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * The outer query hits loop contains the descendant nodes.
      */
     public ScoreNode[][] getMatchingScoreNodes(ScoreNode descendant)
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/EquiJoin.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/EquiJoin.java
index 0edaa1d..b989f0b 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/EquiJoin.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/EquiJoin.java
@@ -56,7 +56,7 @@ public class EquiJoin extends AbstractCondition {
      *
      * @param inner               the inner query hits.
      * @param innerScoreNodeIndex the selector name for the inner query hits.
-     * @param scs                 the sort comparator source.
+     * @param nsMappings          the namespace mappings
      * @param reader              the index reader.
      * @param innerProperty       the name of the property of the inner query
      *                            hits.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/JoinMerger.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/JoinMerger.java
index 82b11c1..99b3fb0 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/JoinMerger.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/JoinMerger.java
@@ -230,7 +230,11 @@ abstract class JoinMerger {
 
             List<Row> rows = new ArrayList<Row>();
             for (Row leftRow : new RowIterable(leftRows)) {
-                for (String value : getLeftValues(leftRow)) {
+                Set<String> leftValues = getLeftValues(leftRow);
+                if(leftValues.isEmpty()){
+                    leftValues.add(null);
+                }
+                for (String value : leftValues) {
                     List<Row> matchingRows = map.get(value);
                     if (matchingRows != null) {
                         for (Row rightRow : matchingRows) {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java
index ea9d2ff..1171677 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/join/QueryEngine.java
@@ -245,6 +245,11 @@ public class QueryEngine {
         Comparator<Row> rightCo = new RowPathComparator(
                 merger.getRightSelectors());
 
+        if (leftRows == null || leftRows.isEmpty()) {
+            return merger.merge(new RowIteratorAdapter((leftRows == null) ? Collections.emptySet() : leftRows),
+                    new RowIteratorAdapter(new TreeSet<Row>()), null, rightCo);
+        }
+
         Set<Row> rightRows = buildRightRowsJoin(csInfo, rightConstraints,
                 isOuterJoin, rightCo, printIndentation + printIndentStep);
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionRegistryImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionRegistryImpl.java
index 24512cd..bc2329c 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionRegistryImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/retention/RetentionRegistryImpl.java
@@ -344,9 +344,9 @@ public class RetentionRegistryImpl implements RetentionRegistry, SynchronousEven
                 // else: not interested in any other property -> ignore.
 
             } catch (RepositoryException e) {
-                log.warn("Internal error while processing event.", e.getMessage());
+                log.warn("Internal error while processing event. {}", e.getMessage());
                 // ignore.
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/DefaultAccessManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/DefaultAccessManager.java
index 52035bc..0327f4c 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/DefaultAccessManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/DefaultAccessManager.java
@@ -62,12 +62,12 @@ import java.util.Set;
  * <li>the Session's represents a system session or a session associated with
  * the repository's administrator</li>
  * </ul>
- * <p/>
+ * <p>
  * It allows to access all available workspaces if
  * <ul>
  * <li>no <code>WorkspaceAccessManager</code> is defined.</li>
  * </ul>
- * <p/>
+ * <p>
  * How access control policies are matched to a particular item is defined by
  * the <code>AccessControlProvider</code> set to this AccessManager.
  *
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/AbstractLoginModule.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/AbstractLoginModule.java
index 222a4e2..5e7b518 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/AbstractLoginModule.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/AbstractLoginModule.java
@@ -47,7 +47,7 @@ import org.slf4j.LoggerFactory;
 /**
  * <code>AbstractLoginModule</code> provides the means for the common
  * authentication tasks within the Repository.
- * <p/>
+ * <p>
  * On successful authentication it associates the credentials to principals
  * using the {@link PrincipalProvider} configured for this LoginModule<p />
  * Jackrabbit distinguishes between Login and Impersonation dispatching the
@@ -72,6 +72,9 @@ public abstract class AbstractLoginModule implements LoginModule {
      * login.
      *
      * @see #isPreAuthenticated(Credentials)
+     * @deprecated For security reasons this configuration option has been
+     * deprecated and will no longer be supported in a subsequent release.
+     * See also <a href="https://issues.apache.org/jira/browse/JCR-3293">JCR-3293</a>
      */
     private static final String PRE_AUTHENTICATED_ATTRIBUTE_OPTION = "trust_credentials_attribute";
 
@@ -87,6 +90,9 @@ public abstract class AbstractLoginModule implements LoginModule {
      * has already been authenticated outside of this LoginModule.
      *
      * @see #getPreAuthAttributeName()
+     * @deprecated For security reasons the support for the preAuth attribute
+     * has been deprecated and will no longer be available in a subsequent release.
+     * See also <a href="https://issues.apache.org/jira/browse/JCR-3293">JCR-3293</a>
      */
     private String preAuthAttributeName;
 
@@ -243,7 +249,7 @@ public abstract class AbstractLoginModule implements LoginModule {
      * <li>{@link #getCredentials()}</li> and
      * <li>{@link #getUserID(Credentials)}</li>
      * </ul>
-     * <p/>
+     * <p>
      *
      * <b>2) User-Principal resolution </b><br>
      * In a second step it is tested, if the resolved User-ID belongs to a User
@@ -282,7 +288,7 @@ public abstract class AbstractLoginModule implements LoginModule {
      * to impersonate to the requested User-ID</li>
      * <li>The user tries to login, but the Credentials can not be verified.</li>
      * </ul>
-     * <p/>
+     * <p>
      * The LoginModule keeps the Credentials and the Principal as instance fields,
      * to mark that login has been successful.
      *
@@ -344,20 +350,20 @@ public abstract class AbstractLoginModule implements LoginModule {
 
     /**
      * Method to commit the authentication process (phase 2).
-     * <p/>
+     * <p>
      * This method is called if the LoginContext's overall authentication
      * succeeded (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
      * LoginModules succeeded).
-     * <p/>
+     * <p>
      * If this LoginModule's own authentication attempt succeeded (checked
      * by retrieving the private state saved by the <code>login</code> method),
      * then this method associates relevant Principals and Credentials with the
      * <code>Subject</code> located in the <code>LoginModule</code>.  If this
      * LoginModule's own authentication attempted failed, then this method
      * removes/destroys any state that was originally saved.
-     * <p/>
+     * <p>
      * The login is considered as succeeded if there is a principal set.
-     * <p/>
+     * <p>
      * The implementation stores the principal associated to the UserID and all
      * the Groups it is member of with the Subject and in addition adds an
      * instance of (#link SimpleCredentials} to the Subject's public credentials.
@@ -380,16 +386,16 @@ public abstract class AbstractLoginModule implements LoginModule {
 
     /**
      * Method to abort the authentication process (phase 2).
-     * <p/>
+     * <p>
      * <p> This method is called if the LoginContext's overall authentication
      * failed. (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL
      * LoginModules did not succeed).
-     * <p/>
+     * <p>
      * <p> If this LoginModule's own authentication attempt succeeded (checked
      * by retrieving the private state saved by the <code>login</code> method),
      * then this method cleans up any state that was originally saved.
-     * <p/>
-     * <p/>
+     * <p>
+     * <p>
      *
      * @return true if this method succeeded, or false if this
      *         <code>LoginModule</code> should be ignored.
@@ -525,7 +531,7 @@ public abstract class AbstractLoginModule implements LoginModule {
      * Method tries to resolve the {@link Credentials} used for login. It takes
      * authentication-extension of an already authenticated {@link Subject} into
      * account.
-     * <p/>
+     * <p>
      * Therefore the credentials are retrieved as follows:
      * <ol>
      * <li>Test if the shared state contains credentials.</li>
@@ -747,6 +753,9 @@ public abstract class AbstractLoginModule implements LoginModule {
      * returns <code>null</code>.
      *
      * @see #isPreAuthenticated(Credentials)
+     * @deprecated For security reasons the support for the preAuth attribute
+     * has been deprecated and will no longer be available in a subsequent release.
+     * See also <a href="https://issues.apache.org/jira/browse/JCR-3293">JCR-3293</a>
      */
     protected final String getPreAuthAttributeName() {
         return preAuthAttributeName;
@@ -768,11 +777,20 @@ public abstract class AbstractLoginModule implements LoginModule {
      * @param creds The Credentials to check
      *
      * @see #getPreAuthAttributeName()
+     * @deprecated For security reasons the support for the preAuth attribute
+     * has been deprecated and will no longer be available in a subsequent release.
+     * See also <a href="https://issues.apache.org/jira/browse/JCR-3293">JCR-3293</a>
      */
     protected boolean isPreAuthenticated(final Credentials creds) {
         final String preAuthAttrName = getPreAuthAttributeName();
-        return preAuthAttrName != null
+        boolean isPreAuth = preAuthAttrName != null
             && (creds instanceof SimpleCredentials)
             && ((SimpleCredentials) creds).getAttribute(preAuthAttrName) != null;
+        if (isPreAuth) {
+            log.warn("Usage of deprecated 'trust_credentials_attribute' option. " +
+                    "Please note that for security reasons this feature will not" +
+                    "be supported in future releases.");
+        }
+        return isPreAuth;
     }
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/CryptedSimpleCredentials.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/CryptedSimpleCredentials.java
index 059850e..c4370ed 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/CryptedSimpleCredentials.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/CryptedSimpleCredentials.java
@@ -17,7 +17,7 @@
 package org.apache.jackrabbit.core.security.authentication;
 
 import org.apache.jackrabbit.core.security.SecurityConstants;
-import org.apache.jackrabbit.util.Text;
+import org.apache.jackrabbit.core.security.user.PasswordUtility;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -25,8 +25,6 @@ import javax.jcr.Credentials;
 import javax.jcr.SimpleCredentials;
 import java.io.UnsupportedEncodingException;
 import java.security.NoSuchAlgorithmException;
-import java.security.MessageDigest;
-import java.security.SecureRandom;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -38,9 +36,6 @@ public class CryptedSimpleCredentials implements Credentials {
 
     private static final Logger log = LoggerFactory.getLogger(CryptedSimpleCredentials.class);
 
-    private final String algorithm;
-    private final String salt;
-
     private final String hashedPassword;
     private final String userId;
     private final Map<String, Object> attributes;
@@ -67,9 +62,7 @@ public class CryptedSimpleCredentials implements Credentials {
             throw new IllegalArgumentException();
         }
         String password = new String(pwd);
-        algorithm = SecurityConstants.DEFAULT_DIGEST;
-        salt = null; // backwards compatibility.
-        hashedPassword = generateHash(password, algorithm, salt);
+        hashedPassword = PasswordUtility.buildPasswordHash(password);
 
         String[] attNames = credentials.getAttributeNames();
         attributes = new HashMap<String, Object>(attNames.length);
@@ -84,8 +77,7 @@ public class CryptedSimpleCredentials implements Credentials {
      * In contrast to {@link CryptedSimpleCredentials(SimpleCredentials)} that
      * expects the password to be plain text this constructor expects the
      * password to be already crypted. However, it performs a simple validation
-     * and calls {@link Text#digest} using the
-     * {@link SecurityConstants#DEFAULT_DIGEST default digest} in case the
+     * and calls {@link PasswordUtility#buildPasswordHash(String)} in case the
      * given password is found to be plain text.
      *
      * @param userId
@@ -101,17 +93,11 @@ public class CryptedSimpleCredentials implements Credentials {
             throw new IllegalArgumentException("Password may not be null.");
         }
         this.userId = userId;
-        String algo =  extractAlgorithm(hashedPassword);
-        if (algo == null) {
+        if (PasswordUtility.isPlainTextPassword(hashedPassword)) {
             // password is plain text (including those starting with {invalidAlgorithm})
-            log.debug("Plain text password -> Using " + SecurityConstants.DEFAULT_DIGEST + " to create digest.");
-            algorithm = SecurityConstants.DEFAULT_DIGEST;
-            salt = generateSalt();
-            this.hashedPassword = generateHash(hashedPassword, algorithm, salt);
+            log.warn("Plain text password -> Using default algorithm to create digest.");
+            this.hashedPassword = PasswordUtility.buildPasswordHash(hashedPassword);
         } else {
-            // password is already hashed and started with {validAlgorithm}
-            algorithm = algo;
-            salt = extractSalt(hashedPassword, algorithm);
             this.hashedPassword = hashedPassword;
         }
         attributes = Collections.emptyMap();
@@ -130,7 +116,7 @@ public class CryptedSimpleCredentials implements Credentials {
     }
 
     public String getAlgorithm() {
-        return algorithm;
+        return PasswordUtility.extractAlgorithm(hashedPassword);
     }
 
     public String getPassword() {
@@ -161,97 +147,10 @@ public class CryptedSimpleCredentials implements Credentials {
 
         if (getUserID().equalsIgnoreCase(credentials.getUserID())) {
             // crypt the password retrieved from the given simple credentials
-            // and test if it is equal to the cryptedPassword field.
-            return hashedPassword.equals(generateHash(String.valueOf(credentials.getPassword()), algorithm, salt));
+            // and test if it is equal to the password hash defined with this
+            // CryptedSimpleCredentials instance.
+            return PasswordUtility.isSame(hashedPassword, String.valueOf(credentials.getPassword()));
         }
         return false;
     }
-
-    /**
-     * @param pwd Plain text password
-     * @param algorithm The algorithm to be used for the digest.
-     * @param salt The salt to be used for the digest.
-     * @return Digest of the given password with leading algorithm and optionally
-     * salt information.
-     * @throws NoSuchAlgorithmException
-     * @throws UnsupportedEncodingException
-     */
-    private static String generateHash(String pwd, String algorithm, String salt)
-            throws NoSuchAlgorithmException, UnsupportedEncodingException {
-
-        StringBuilder password = new StringBuilder();
-        password.append("{").append(algorithm).append("}");
-        if (salt != null && salt.length() > 0) {
-            password.append(salt).append("-");
-            StringBuilder data = new StringBuilder();
-            data.append(salt).append(pwd);
-            password.append(Text.digest(algorithm, data.toString().getBytes("UTF-8")));
-        } else {
-            password.append(Text.digest(algorithm, pwd.getBytes("UTF-8")));            
-        }
-        return password.toString();
-    }
-
-    /**
-     * Extract the algorithm from the given crypted password string. Returns the
-     * algorithm or <code>null</code> if the given string doesn't have a
-     * leading <code>{algorithm}</code> such as created by {@link #generateHash(String, String, String)
-     * or if the extracted string doesn't represent an available algorithm.
-     *
-     * @param hashedPwd
-     * @return The algorithm or <code>null</code> if the given string doesn't have a
-     * leading <code>{algorith}</code> such as created by {@link #crypt(String, String)
-     * or if the extracted string isn't an available algorithm. 
-     */
-    private static String extractAlgorithm(String hashedPwd) {
-        int end = hashedPwd.indexOf('}');
-        if (hashedPwd.startsWith("{") && end > 0) {
-            String algorithm = hashedPwd.substring(1, end);
-            try {
-                MessageDigest.getInstance(algorithm);
-                return algorithm;
-            } catch (NoSuchAlgorithmException e) {
-                log.debug("Invalid algorithm detected " + algorithm);
-            }
-        }
-
-        // not starting with {} or invalid algorithm
-        return null;
-    }
-
-    /**
-     * Extract the salt from the password hash.
-     *
-     * @param hashedPwd
-     * @param algorithm
-     * @return salt or <code>null</code>
-     */
-    private static String extractSalt(String hashedPwd, String algorithm) {
-        int start = algorithm.length()+2;
-        int end = hashedPwd.indexOf('-', start);
-        if (end > -1) {
-            return hashedPwd.substring(start, end);
-        }
-
-        // no salt 
-        return null;
-    }
-
-    /**
-     * Generate a new random salt for password digest.
-     *
-     * @return a new random salt.
-     */
-    private static String generateSalt() {
-        SecureRandom random = new SecureRandom();
-        byte salt[] = new byte[8];
-        random.nextBytes(salt);
-
-        StringBuffer res = new StringBuffer(salt.length * 2);
-        for (byte b : salt) {
-            res.append(Text.hexTable[(b >> 4) & 15]);
-            res.append(Text.hexTable[b & 15]);
-        }
-        return res.toString();
-    }
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/DefaultLoginModule.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/DefaultLoginModule.java
index b4f690b..7a0362f 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/DefaultLoginModule.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/DefaultLoginModule.java
@@ -16,29 +16,25 @@
  */
 package org.apache.jackrabbit.core.security.authentication;
 
+import java.security.Principal;
+import java.util.Map;
+import javax.jcr.Credentials;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.FailedLoginException;
+import javax.security.auth.login.LoginException;
+
 import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
-import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.api.security.user.UserManager;
-import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.security.authentication.token.TokenBasedAuthentication;
-import org.apache.jackrabbit.core.security.user.UserImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.jcr.Credentials;
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.security.auth.Subject;
-import javax.security.auth.callback.CallbackHandler;
-import javax.security.auth.login.FailedLoginException;
-import javax.security.auth.login.LoginException;
-import java.security.Principal;
-import java.util.Map;
-
 /**
  * The <code>DefaultLoginModule</code> authenticates Credentials related to
  * a {@link User} of the Repository<br>
@@ -50,7 +46,7 @@ import java.util.Map;
  * </ul>
  * In both cases the login is successful if the system contains a non-disabled,
  * valid user that matches the given credentials.
- * <p/>
+ * <p>
  * Correspondingly impersonation is delegated to the <code>User</code>'s
  * {@link User#getImpersonation() Impersonation} object.
  *
@@ -160,7 +156,7 @@ public class DefaultLoginModule extends AbstractLoginModule {
                 tokenExpiration = Long.parseLong(options.get(PARAM_TOKEN_EXPIRATION).toString());
                 log.debug("- Token expiration -> '" + tokenExpiration + "'");
             } catch (NumberFormatException e) {
-                log.warn("Unabled to parse token expiration: ", e.getMessage());
+                log.warn("Unabled to parse token expiration: {}", e.getMessage());
             }
         }
     }
@@ -192,7 +188,7 @@ public class DefaultLoginModule extends AbstractLoginModule {
             }
         } catch (RepositoryException e) {
             // should not get here
-            log.warn("Error while retrieving principal.", e.getMessage());
+            log.warn("Error while retrieving principal. {}", e.getMessage());
         }
         return principal;
     }
@@ -229,20 +225,7 @@ public class DefaultLoginModule extends AbstractLoginModule {
             // special token based login
             tokenCredentials = ((TokenCredentials) credentials);
             try {
-                Node n = TokenBasedAuthentication.getTokenNode(tokenCredentials, session);
-                final NodeImpl userNode = (NodeImpl) n.getParent().getParent();
-                final String principalName = userNode.getProperty(UserImpl.P_PRINCIPAL_NAME).getString();
-                if (userNode.isNodeType(UserImpl.NT_REP_USER)) {
-                    Authorizable a = userManager.getAuthorizable(new ItemBasedPrincipal() {
-                        public String getPath() throws RepositoryException {
-                            return userNode.getPath();
-                        }
-                        public String getName() {
-                            return principalName;
-                        }
-                    });
-                    return a.getID();
-                }
+                return TokenBasedAuthentication.getUserId(tokenCredentials, session);
             } catch (RepositoryException e) {
                 if (log.isDebugEnabled()) {
                     log.warn("Failed to retrieve UserID from token-based credentials", e);
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/CompatTokenProvider.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/CompatTokenProvider.java
new file mode 100644
index 0000000..900c2ed
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/CompatTokenProvider.java
@@ -0,0 +1,427 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.security.authentication.token;
+
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Map;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
+import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.id.NodeIdFactory;
+import org.apache.jackrabbit.core.security.SecurityConstants;
+import org.apache.jackrabbit.core.security.user.UserImpl;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.util.ISO8601;
+import org.apache.jackrabbit.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Backport of the TokenProvider implementation present with OAK adjusted to
+ * match some subtle differences in jackrabbit token login.
+ */
+class CompatTokenProvider {
+
+    private static final Logger log = LoggerFactory.getLogger(CompatTokenProvider.class);
+
+    private static final String TOKEN_ATTRIBUTE = ".token";
+    private static final String TOKEN_ATTRIBUTE_EXPIRY = TOKEN_ATTRIBUTE + ".exp";
+    private static final String TOKEN_ATTRIBUTE_KEY = TOKEN_ATTRIBUTE + ".key";
+    private static final String TOKENS_NODE_NAME = ".tokens";
+    private static final String TOKENS_NT_NAME = "nt:unstructured"; // TODO: configurable
+
+    private static final char DELIM = '_';
+
+    private final SessionImpl session;
+    private final UserManager userManager;
+    private final long tokenExpiration;
+
+    CompatTokenProvider(SessionImpl session, long tokenExpiration) throws RepositoryException {
+        this.session = session;
+        this.userManager = session.getUserManager();
+        this.tokenExpiration = tokenExpiration;
+    }
+
+    /**
+     * Create a separate token node underneath a dedicated token store within
+     * the user home node. That token node contains the hashed token, the
+     * expiration time and additional mandatory attributes that will be verified
+     * during login.
+     *
+     * @param user
+     * @param sc The current simple credentials.
+     * @return A new {@code TokenInfo} or {@code null} if the token could not
+     *         be created.
+     */
+    public TokenInfo createToken(User user, SimpleCredentials sc) throws RepositoryException {
+        String userPath = null;
+        Principal pr = user.getPrincipal();
+        if (pr instanceof ItemBasedPrincipal) {
+            userPath = ((ItemBasedPrincipal) pr).getPath();
+        }
+
+        TokenCredentials tokenCredentials;
+        if (userPath != null && session.nodeExists(userPath)) {
+            Node userNode = session.getNode(userPath);
+            Node tokenParent;
+            if (!userNode.hasNode(TOKENS_NODE_NAME)) {
+                userNode.addNode(TOKENS_NODE_NAME, TOKENS_NT_NAME);
+                try {
+                    session.save();
+                } catch (RepositoryException e) {
+                    // may happen when .tokens node is created concurrently
+                    session.refresh(false);
+                }
+            }
+            tokenParent = userNode.getNode(TOKENS_NODE_NAME);
+
+            long creationTime = new Date().getTime();
+            long expirationTime = creationTime + tokenExpiration;
+
+            Calendar cal = GregorianCalendar.getInstance();
+            cal.setTimeInMillis(creationTime);
+
+            // generate key part of the login token
+            String key = generateKey(8);
+
+            // create the token node
+            String tokenName = Text.replace(ISO8601.format(cal), ":", ".");
+            Node tokenNode;
+            // avoid usage of sequential nodeIDs
+            if (System.getProperty(NodeIdFactory.SEQUENTIAL_NODE_ID) == null) {
+                tokenNode = tokenParent.addNode(tokenName);
+            } else {
+                tokenNode = ((NodeImpl) tokenParent).addNodeWithUuid(tokenName, NodeId.randomId().toString());
+            }
+
+            StringBuilder sb = new StringBuilder(tokenNode.getIdentifier());
+            sb.append(DELIM).append(key);
+
+            String token = sb.toString();
+            tokenCredentials = new TokenCredentials(token);
+            sc.setAttribute(TOKEN_ATTRIBUTE, token);
+
+            // add key property
+            tokenNode.setProperty(TOKEN_ATTRIBUTE_KEY, getDigestedKey(key));
+
+            // add expiration time property
+            cal.setTimeInMillis(expirationTime);
+            tokenNode.setProperty(TOKEN_ATTRIBUTE_EXPIRY, session.getValueFactory().createValue(cal));
+
+            // add additional attributes passed in by the credentials.
+            for (String name : sc.getAttributeNames()) {
+                if (!TOKEN_ATTRIBUTE.equals(name)) {
+                    String value = sc.getAttribute(name).toString();
+                    tokenNode.setProperty(name, value);
+                    tokenCredentials.setAttribute(name, value);
+                }
+            }
+            session.save();
+            return new CompatModeInfo(token, tokenNode);
+        } else {
+            throw new RepositoryException("Cannot create login token: No corresponding node for User " + user.getID() +" in workspace '" + session.getWorkspace().getName() + "'.");
+        }
+    }
+
+    /**
+     * Retrieves the token information associated with the specified login
+     * token. If no accessible {@code Tree} exists for the given token or if
+     * the token is not associated with a valid user this method returns {@code null}.
+     *
+     * @param token A valid login token.
+     * @return The {@code TokenInfo} associated with the specified token or
+     *         {@code null} of the corresponding information does not exist or is not
+     *         associated with a valid user.
+     */
+    public TokenInfo getTokenInfo(String token) throws RepositoryException {
+        if (token == null) {
+            return null;
+        }
+        NodeImpl tokenNode = (NodeImpl) getTokenNode(token, session);
+        String userId = getUserId(tokenNode, userManager);
+        if (userId == null || !isValidTokenTree(tokenNode)) {
+            return null;
+        } else {
+            return new CompatModeInfo(token);
+        }
+    }
+
+    static Node getTokenNode(String token, Session session) throws RepositoryException {
+        int pos = token.indexOf(DELIM);
+        String id = (pos == -1) ? token : token.substring(0, pos);
+        return session.getNodeByIdentifier(id);
+    }
+
+    public static String getUserId(TokenCredentials tokenCredentials, Session session) throws RepositoryException {
+        if (!(session instanceof JackrabbitSession)) {
+            throw new RepositoryException("JackrabbitSession expected");
+        }
+        NodeImpl n = (NodeImpl) getTokenNode(tokenCredentials.getToken(), session);
+        return getUserId(n, ((JackrabbitSession) session).getUserManager());
+    }
+
+    private static String getUserId(NodeImpl tokenNode, UserManager userManager) throws RepositoryException {
+        if (tokenNode != null) {
+            final NodeImpl userNode = (NodeImpl) tokenNode.getParent().getParent();
+            final String principalName = userNode.getProperty(UserImpl.P_PRINCIPAL_NAME).getString();
+            if (userNode.isNodeType(UserImpl.NT_REP_USER)) {
+                Authorizable a = userManager.getAuthorizable(new ItemBasedPrincipal() {
+                    public String getPath() throws RepositoryException {
+                        return userNode.getPath();
+                    }
+
+                    public String getName() {
+                        return principalName;
+                    }
+                });
+                if (a != null && !a.isGroup() && !((User)a).isDisabled()) {
+                    return a.getID();
+                }
+            } else {
+                throw new RepositoryException("Failed to calculate userId from token credentials");
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns {@code true} if the specified {@code attributeName}
+     * starts with or equals {@link #TOKEN_ATTRIBUTE}.
+     *
+     * @param attributeName The attribute name.
+     * @return {@code true} if the specified {@code attributeName}
+     *         starts with or equals {@link #TOKEN_ATTRIBUTE}.
+     */
+    static boolean isMandatoryAttribute(String attributeName) {
+        return attributeName != null && attributeName.startsWith(TOKEN_ATTRIBUTE);
+    }
+
+    /**
+     * Returns <code>false</code> if the specified attribute name doesn't have
+     * a 'jcr' or 'rep' namespace prefix; <code>true</code> otherwise. This is
+     * a lazy evaluation in order to avoid testing the defining node type of
+     * the associated jcr property.
+     *
+     * @param propertyName
+     * @return <code>true</code> if the specified property name doesn't seem
+     * to represent repository internal information.
+     */
+    private static boolean isInfoAttribute(String propertyName) {
+        String prefix = Text.getNamespacePrefix(propertyName);
+        return !Name.NS_JCR_PREFIX.equals(prefix) && !Name.NS_REP_PREFIX.equals(prefix);
+    }
+
+    private static boolean isValidTokenTree(NodeImpl tokenNode) throws RepositoryException {
+        if (tokenNode == null) {
+            return false;
+        } else {
+            return TOKENS_NODE_NAME.equals(tokenNode.getParent().getName());
+        }
+    }
+
+    private static String generateKey(int size) {
+        SecureRandom random = new SecureRandom();
+        byte key[] = new byte[size];
+        random.nextBytes(key);
+
+        StringBuffer res = new StringBuffer(key.length * 2);
+        for (byte b : key) {
+            res.append(Text.hexTable[(b >> 4) & 15]);
+            res.append(Text.hexTable[b & 15]);
+        }
+        return res.toString();
+    }
+
+    private static String getDigestedKey(TokenCredentials tc) throws RepositoryException {
+        String tk = tc.getToken();
+        int pos = tk.indexOf(DELIM);
+        if (pos > -1) {
+            return getDigestedKey(tk.substring(pos+1));
+        }
+        return null;
+    }
+
+    private static String getDigestedKey(String key) throws RepositoryException {
+        try {
+            StringBuilder sb = new StringBuilder();
+            sb.append("{").append(SecurityConstants.DEFAULT_DIGEST).append("}");
+            sb.append(Text.digest(SecurityConstants.DEFAULT_DIGEST, key, "UTF-8"));
+            return sb.toString();
+        } catch (NoSuchAlgorithmException e) {
+            throw new RepositoryException("Failed to generate login token.");
+        } catch (UnsupportedEncodingException e) {
+            throw new RepositoryException("Failed to generate login token.");
+        }
+    }
+
+    private final class CompatModeInfo implements TokenInfo {
+
+        private final String token;
+
+        private final Map<String, String> attributes;
+        private final Map<String, String> info;
+        private final long expiry;
+        private final String key;
+
+        private CompatModeInfo(String token) throws RepositoryException {
+            this(token, getTokenNode(token, session));
+        }
+
+        private CompatModeInfo(String token, Node n) throws RepositoryException {
+            this.token = token;
+            long expTime = Long.MAX_VALUE;
+            String keyV = null;
+            if (token != null) {
+                attributes = new HashMap<String, String>();
+                info = new HashMap<String, String>();
+
+                PropertyIterator it = n.getProperties();
+                while (it.hasNext()) {
+                    Property p = it.nextProperty();
+                    String name = p.getName();
+                    if (TOKEN_ATTRIBUTE_EXPIRY.equals(name)) {
+                        expTime = p.getLong();
+                    } else if (TOKEN_ATTRIBUTE_KEY.equals(name)) {
+                        keyV = p.getString();
+                    } else if (isMandatoryAttribute(name)) {
+                        attributes.put(name, p.getString());
+                    } else if (isInfoAttribute(name)) {
+                        info.put(name, p.getString());
+                    } // else: jcr property -> ignore
+                }
+            } else {
+                attributes = Collections.emptyMap();
+                info = Collections.emptyMap();
+            }
+            expiry = expTime;
+            key = keyV;
+        }
+
+        public String getToken() {
+            return token;
+        }
+
+        public boolean isExpired(long loginTime) {
+            return expiry < loginTime;
+        }
+
+        public boolean remove() {
+            Session s = null;
+            try {
+                s = ((SessionImpl) session).createSession(session.getWorkspace().getName());
+                Node tokenNode = getTokenNode(token, s);
+
+                tokenNode.remove();
+                s.save();
+                return true;
+            } catch (RepositoryException e) {
+                log.warn("Internal error while removing token node.", e);
+            } finally {
+                if (s != null) {
+                    s.logout();
+                }
+            }
+            return false;
+        }
+
+        public boolean matches(TokenCredentials tokenCredentials) throws RepositoryException {
+            // test for matching key
+            if (key != null && !key.equals(getDigestedKey(tokenCredentials))) {
+                return false;
+            }
+
+            // check if all other required attributes match
+            for (String name : attributes.keySet()) {
+                if (!attributes.get(name).equals(tokenCredentials.getAttribute(name))) {
+                    // no match -> login fails.
+                    return false;
+                }
+            }
+
+            // update set of informative attributes on the credentials
+            // based on the properties present on the token node.
+            Collection<String> attrNames = Arrays.asList(tokenCredentials.getAttributeNames());
+            for (String key : info.keySet()) {
+                if (!attrNames.contains(key)) {
+                    tokenCredentials.setAttribute(key, info.get(key));
+                }
+            }
+
+            return true;
+        }
+
+        public boolean resetExpiration(long loginTime) throws RepositoryException {
+            Node tokenNode;
+            Session s = null;
+            try {
+                // expiry...
+                if (expiry - loginTime <= tokenExpiration/2) {
+                    long expirationTime = loginTime + tokenExpiration;
+                    Calendar cal = GregorianCalendar.getInstance();
+                    cal.setTimeInMillis(expirationTime);
+
+                    s = ((SessionImpl) session).createSession(session.getWorkspace().getName());
+                    tokenNode = getTokenNode(token, s);
+                    tokenNode.setProperty(TOKEN_ATTRIBUTE_EXPIRY, s.getValueFactory().createValue(cal));
+                    s.save();
+                    return true;
+                }
+            } catch (RepositoryException e) {
+                log.warn("Failed to update expiry or informative attributes of token node.", e);
+            } finally {
+                if (s != null) {
+                    s.logout();
+                }
+            }
+            return false;
+        }
+
+        public TokenCredentials getCredentials() {
+            TokenCredentials tc = new TokenCredentials(token);
+            for (String name : attributes.keySet()) {
+                tc.setAttribute(name, attributes.get(name));
+            }
+            for (String name : info.keySet()) {
+                tc.setAttribute(name, info.get(name));
+            }
+            return tc;
+        }
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthentication.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthentication.java
index f029f76..d578d21 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthentication.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthentication.java
@@ -16,41 +16,22 @@
  */
 package org.apache.jackrabbit.core.security.authentication.token;
 
+import java.util.Date;
+import javax.jcr.Credentials;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
 import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
-import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
 import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.SessionImpl;
-import org.apache.jackrabbit.core.id.NodeId;
-import org.apache.jackrabbit.core.id.NodeIdFactory;
-import org.apache.jackrabbit.core.security.SecurityConstants;
 import org.apache.jackrabbit.core.security.authentication.Authentication;
-import org.apache.jackrabbit.spi.Name;
-import org.apache.jackrabbit.util.ISO8601;
-import org.apache.jackrabbit.util.Text;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.jcr.Credentials;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-import java.io.UnsupportedEncodingException;
-import java.security.NoSuchAlgorithmException;
-import java.security.Principal;
-import java.security.SecureRandom;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.Map;
-
 /**
  * Authentication implementation that compares the tokens stored with a
  * given user node to the token present in the SimpleCredentials attributes.
@@ -72,60 +53,29 @@ public class TokenBasedAuthentication implements Authentication {
      */
     public static final String TOKEN_ATTRIBUTE = ".token";
 
-    private static final String TOKEN_ATTRIBUTE_EXPIRY = TOKEN_ATTRIBUTE + ".exp";
-    private static final String TOKEN_ATTRIBUTE_KEY = TOKEN_ATTRIBUTE + ".key";
-    private static final String TOKENS_NODE_NAME = ".tokens";
-    private static final String TOKENS_NT_NAME = "nt:unstructured"; // TODO: configurable
-
-    private static final char DELIM = '_';
-
-    private final String token;
-    private final long tokenExpiration;
-    private final Session session;
+    /**
+     * @deprecated This system parameter allows to enable backwards compatible
+     * behavior of the {@code TokenBasedAuthentication}. Note that as of OAK 1.0
+     * this flag will no be supported.
+     */
+    public static final String PARAM_COMPAT = "TokenCompatMode";
 
-    private final Map<String, String> attributes;
-    private final Map<String, String> info;
-    private final long expiry;
-    private final String key;
+    private final TokenInfo tokenInfo;
 
     public TokenBasedAuthentication(String token, long tokenExpiration, Session session) throws RepositoryException {
-        this.session = session;
-        this.tokenExpiration = tokenExpiration;
-        this.token = token;
-        long expTime = Long.MAX_VALUE;
-        String keyV = null;
-        if (token != null) {
-            attributes = new HashMap<String, String>();
-            info = new HashMap<String, String>();
-
-            Node n = getTokenNode(token, session);
-            PropertyIterator it = n.getProperties();
-            while (it.hasNext()) {
-                Property p = it.nextProperty();
-                String name = p.getName();
-                if (TOKEN_ATTRIBUTE_EXPIRY.equals(name)) {
-                    expTime = p.getLong();
-                } else if (TOKEN_ATTRIBUTE_KEY.equals(name)) {
-                    keyV = p.getString();
-                } else if (isMandatoryAttribute(name)) {
-                    attributes.put(name, p.getString());
-                } else if (isInfoAttribute(name)) {
-                    info.put(name, p.getString());
-                } // else: jcr property -> ignore
-            }
+        if (compatMode()) {
+            this.tokenInfo = new CompatTokenProvider((SessionImpl) session, tokenExpiration).getTokenInfo(token);
         } else {
-            attributes = Collections.emptyMap();
-            info = Collections.emptyMap();
+            this.tokenInfo = new TokenProvider((SessionImpl) session, tokenExpiration).getTokenInfo(token);
         }
-        expiry = expTime;
-        key = keyV;
+
     }
 
     /**
      * @see Authentication#canHandle(javax.jcr.Credentials)
      */
     public boolean canHandle(Credentials credentials) {
-        return token != null && isTokenBasedLogin(credentials);
+        return tokenInfo != null && isTokenBasedLogin(credentials);
     }
 
     /**
@@ -136,105 +86,31 @@ public class TokenBasedAuthentication implements Authentication {
             throw new RepositoryException("TokenCredentials expected. Cannot handle " + credentials.getClass().getName());
         }
         TokenCredentials tokenCredentials = (TokenCredentials) credentials;
+        return validateCredentials(tokenCredentials);
+    }
 
-        // credentials without userID -> check if attributes provide
-        // sufficient information for successful authentication.
-        if (token.equals(tokenCredentials.getToken())) {
-            long loginTime = new Date().getTime();
-            // test if the token has already expired
-            if (expiry < loginTime) {
-                // already expired -> login fails.
-                // ... remove the expired token node before aborting the login
-                removeToken();
-                return false;
-            }
-
-            // test for matching key
-            if (key != null && !key.equals(getDigestedKey(tokenCredentials))) {
-                return false;
-            }
-
-            // check if all other required attributes match
-            for (String name : attributes.keySet()) {
-                if (!attributes.get(name).equals(tokenCredentials.getAttribute(name))) {
-                    // no match -> login fails.
-                    return false;
-                }
-            }
-
-            // update set of informative attributes on the credentials
-            // based on the properties present on the token node.
-            Collection<String> attrNames = Arrays.asList(tokenCredentials.getAttributeNames());
-            for (String key : info.keySet()) {
-                if (!attrNames.contains(key)) {
-                    tokenCredentials.setAttribute(key, info.get(key));
-                }
-            }
+    private boolean validateCredentials(TokenCredentials tokenCredentials) throws RepositoryException {
+        if (tokenInfo == null) {
+            log.debug("No valid TokenInfo for token.");
+            return false;
+        }
 
-            // update token node if required: optionally resetting the expiration
-            updateTokenNode(expiry, loginTime);
+        long loginTime = new Date().getTime();
+        if (tokenInfo.isExpired(loginTime)) {
+            // token is expired
+            log.debug("Token is expired");
+            tokenInfo.remove();
+            return false;
+        }
 
+        if (tokenInfo.matches(tokenCredentials)) {
+            tokenInfo.resetExpiration(loginTime);
             return true;
         }
 
-        // wrong credentials that cannot be compared by this authentication
         return false;
     }
 
-    /**
-     * Performs the following checks/updates:
-     * <ol>
-     * <li>Reset the expiration if half of the expiration has passed in order to
-     * minimize write operations (avoid resetting upon each login).</li>
-     * </ol>
-     *
-     * @param tokenExpiry
-     * @param loginTime
-     */
-    private void updateTokenNode(long tokenExpiry, long loginTime) {
-        Node tokenNode;
-        Session s = null;
-        try {
-            // expiry...
-            if (tokenExpiry - loginTime <= tokenExpiration/2) {
-                long expirationTime = loginTime + tokenExpiration;
-                Calendar cal = GregorianCalendar.getInstance();
-                cal.setTimeInMillis(expirationTime);
-
-                s = ((SessionImpl) session).createSession(session.getWorkspace().getName());
-                tokenNode = getTokenNode(token, s);
-                tokenNode.setProperty(TOKEN_ATTRIBUTE_EXPIRY, s.getValueFactory().createValue(cal));
-                s.save();
-            }
-        } catch (RepositoryException e) {
-            log.warn("Failed to update expiry or informative attributes of token node.", e);
-        } finally {
-            if (s != null) {
-                s.logout();
-            }
-        }
-    }
-
-    /**
-     * Remove the node associated with the expired token defined by this TokenBasedAuthentication.
-     */
-    private void removeToken() {
-        Session s = null;
-        try {
-            s = ((SessionImpl) session).createSession(session.getWorkspace().getName());
-            Node tokenNode = getTokenNode(token, s);
-            
-            tokenNode.remove();
-            s.save();
-        } catch (RepositoryException e) {
-            log.warn("Internal error while removing token node.", e);
-        } finally {
-            if (s != null) {
-                s.logout();
-            }
-        }
-    }
-
     //--------------------------------------------------------------------------
     /**
      * Returns <code>true</code> if the given <code>credentials</code> object
@@ -251,28 +127,17 @@ public class TokenBasedAuthentication implements Authentication {
     /**
      * Returns <code>true</code> if the specified <code>attributeName</code>
      * starts with or equals {@link #TOKEN_ATTRIBUTE}.
-     *  
+     *
      * @param attributeName
      * @return <code>true</code> if the specified <code>attributeName</code>
      * starts with or equals {@link #TOKEN_ATTRIBUTE}.
      */
     public static boolean isMandatoryAttribute(String attributeName) {
-        return attributeName != null && attributeName.startsWith(TOKEN_ATTRIBUTE);
-    }
-
-    /**
-     * Returns <code>false</code> if the specified attribute name doesn't have
-     * a 'jcr' or 'rep' namespace prefix; <code>true</code> otherwise. This is
-     * a lazy evaluation in order to avoid testing the defining node type of
-     * the associated jcr property.
-     *
-     * @param propertyName
-     * @return <code>true</code> if the specified property name doesn't seem
-     * to represent repository internal information.
-     */
-    private static boolean isInfoAttribute(String propertyName) {
-        String prefix = Text.getNamespacePrefix(propertyName);
-        return !Name.NS_JCR_PREFIX.equals(prefix) && !Name.NS_REP_PREFIX.equals(prefix);
+        if (compatMode()) {
+            return CompatTokenProvider.isMandatoryAttribute(attributeName);
+        } else {
+            return TokenProvider.isMandatoryAttribute(attributeName);
+        }
     }
 
     /**
@@ -304,118 +169,49 @@ public class TokenBasedAuthentication implements Authentication {
      * specified user in the current workspace or if an error occurs while
      * creating the token node.
      */
-    public synchronized static Credentials createToken(User user, SimpleCredentials credentials,
-                                                       long tokenExpiration, Session session) throws RepositoryException {
+    public static Credentials createToken(User user, SimpleCredentials credentials,
+                                          long tokenExpiration, Session session) throws RepositoryException {
         String workspaceName = session.getWorkspace().getName();
         if (user == null) {
             throw new RepositoryException("Cannot create login token: No corresponding node for 'null' user in workspace '" + workspaceName + "'.");
         }
-        String userPath = null;
-        Principal pr = user.getPrincipal();
-        if (pr instanceof ItemBasedPrincipal) {
-            userPath = ((ItemBasedPrincipal) pr).getPath();
-        }
-
-        TokenCredentials tokenCredentials;
-        if (userPath != null && session.nodeExists(userPath)) {
-            Node userNode = session.getNode(userPath);
-            Node tokenParent;
-            if (userNode.hasNode(TOKENS_NODE_NAME)) {
-                tokenParent = userNode.getNode(TOKENS_NODE_NAME);
-            } else {
-                tokenParent = userNode.addNode(TOKENS_NODE_NAME, TOKENS_NT_NAME);
-            }
-
-            long creationTime = new Date().getTime();
-            long expirationTime = creationTime + tokenExpiration;
 
-            Calendar cal = GregorianCalendar.getInstance();
-            cal.setTimeInMillis(creationTime);
-
-            // generate key part of the login token
-            String key = generateKey(8);
-
-            // create the token node
-            String tokenName = Text.replace(ISO8601.format(cal), ":", ".");
-            Node tokenNode;
-            // avoid usage of sequential nodeIDs
-            if (System.getProperty(NodeIdFactory.SEQUENTIAL_NODE_ID) == null) {
-                tokenNode = tokenParent.addNode(tokenName);
-            } else {
-                tokenNode = ((NodeImpl) tokenParent).addNodeWithUuid(tokenName, NodeId.randomId().toString());
-            }
-
-            StringBuilder sb = new StringBuilder(tokenNode.getIdentifier());
-            sb.append(DELIM).append(key);
-
-            String token = sb.toString();
-            tokenCredentials = new TokenCredentials(token);
-            credentials.setAttribute(TOKEN_ATTRIBUTE, token);
-
-            // add key property
-            tokenNode.setProperty(TOKEN_ATTRIBUTE_KEY, getDigestedKey(key));
-
-            // add expiration time property
-            cal.setTimeInMillis(expirationTime);
-            tokenNode.setProperty(TOKEN_ATTRIBUTE_EXPIRY, session.getValueFactory().createValue(cal));
+        TokenInfo ti;
+        if (compatMode()) {
+            ti = new CompatTokenProvider((SessionImpl) session, tokenExpiration).createToken(user, credentials);
+        } else {
+            ti = new TokenProvider((SessionImpl) session, tokenExpiration).createToken(user, credentials);
+        }
 
-            // add additional attributes passed in by the credentials.
-            for (String name : credentials.getAttributeNames()) {
-                if (!TOKEN_ATTRIBUTE.equals(name)) {
-                    String value = credentials.getAttribute(name).toString();
-                    tokenNode.setProperty(name, value);
-                    tokenCredentials.setAttribute(name, value);
-                }
-            }
-            session.save();
-            return tokenCredentials;
+        if (ti != null) {
+            return ti.getCredentials();
         } else {
-            throw new RepositoryException("Cannot create login token: No corresponding node for User " + user.getID() +" in workspace '" + workspaceName + "'.");
+            throw new RepositoryException("Cannot create login token.");
         }
     }
 
     public static Node getTokenNode(TokenCredentials credentials, Session session) throws RepositoryException {
-        return getTokenNode(credentials.getToken(), session);
-    }
-
-    private static Node getTokenNode(String token, Session session) throws RepositoryException {
-        int pos = token.indexOf(DELIM);
-        String id = (pos == -1) ? token : token.substring(0, pos);
-        return session.getNodeByIdentifier(id);
+        if (compatMode()) {
+            return CompatTokenProvider.getTokenNode(credentials.getToken(), session);
+        } else {
+            return TokenProvider.getTokenNode(credentials.getToken(), session);
+        }
     }
 
-    private static String generateKey(int size) {
-        SecureRandom random = new SecureRandom();
-        byte key[] = new byte[size];
-        random.nextBytes(key);
 
-        StringBuffer res = new StringBuffer(key.length * 2);
-        for (byte b : key) {
-            res.append(Text.hexTable[(b >> 4) & 15]);
-            res.append(Text.hexTable[b & 15]);
+    public static String getUserId(TokenCredentials tokenCredentials, Session session) throws RepositoryException {
+        if (compatMode()) {
+            return CompatTokenProvider.getUserId(tokenCredentials, session);
+        } else {
+            if (!(session instanceof JackrabbitSession)) {
+                throw new RepositoryException("JackrabbitSession expected");
+            }
+            NodeImpl n = (NodeImpl) getTokenNode(tokenCredentials, session);
+            return TokenProvider.getUserId(n, ((JackrabbitSession) session).getUserManager());
         }
-        return res.toString();
-    }
-
-    private static String getDigestedKey(TokenCredentials tc) throws RepositoryException {
-        String tk = tc.getToken();
-        int pos = tk.indexOf(DELIM);
-        if (pos > -1) {
-            return getDigestedKey(tk.substring(pos+1));
-        }     
-        return null;
     }
 
-    private static String getDigestedKey(String key) throws RepositoryException {
-        try {
-            StringBuilder sb = new StringBuilder();
-            sb.append("{").append(SecurityConstants.DEFAULT_DIGEST).append("}");
-            sb.append(Text.digest(SecurityConstants.DEFAULT_DIGEST, key, "UTF-8"));
-            return sb.toString();
-        } catch (NoSuchAlgorithmException e) {
-            throw new RepositoryException("Failed to generate login token.");
-        } catch (UnsupportedEncodingException e) {
-            throw new RepositoryException("Failed to generate login token.");
-        }
+    private static boolean compatMode() {
+        return Boolean.parseBoolean(System.getProperty(PARAM_COMPAT));
     }
 }
\ No newline at end of file
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenInfo.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenInfo.java
new file mode 100644
index 0000000..ebddb16
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenInfo.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.security.authentication.token;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
+
+/**
+ * TokenInfo... TODO
+ */
+interface TokenInfo {
+
+    String getToken();
+
+
+    boolean isExpired(long loginTime);
+
+    boolean remove();
+
+    boolean matches(TokenCredentials tokenCredentials) throws RepositoryException;
+
+    boolean resetExpiration(long loginTime) throws RepositoryException;
+
+    TokenCredentials getCredentials();
+}
\ No newline at end of file
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenProvider.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenProvider.java
new file mode 100644
index 0000000..f9d8a0c
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authentication/token/TokenProvider.java
@@ -0,0 +1,478 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.security.authentication.token;
+
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.SecureRandom;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.jcr.AccessDeniedException;
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+
+import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
+import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.ProtectedItemModifier;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.security.user.PasswordUtility;
+import org.apache.jackrabbit.core.security.user.UserImpl;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.util.ISO8601;
+import org.apache.jackrabbit.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Backport of the TokenProvider implementation present with OAK adjusted to
+ * match some subtle differences in jackrabbit token login.
+ */
+public class TokenProvider extends ProtectedItemModifier {
+
+    private static final Logger log = LoggerFactory.getLogger(TokenProvider.class);
+
+    private static final String TOKEN_ATTRIBUTE = ".token";
+    private static final String TOKEN_ATTRIBUTE_EXPIRY = "rep:token.exp";
+    private static final String TOKEN_ATTRIBUTE_KEY = "rep:token.key";
+    private static final String TOKENS_NODE_NAME = ".tokens";
+    private static final String TOKEN_NT_NAME = "rep:Token";
+    private static final Name TOKENS_NT_NAME = NameConstants.NT_UNSTRUCTURED;
+
+    private static final char DELIM = '_';
+
+    private static final Set<String> RESERVED_ATTRIBUTES = new HashSet(3);
+    static {
+        RESERVED_ATTRIBUTES.add(TOKEN_ATTRIBUTE);
+        RESERVED_ATTRIBUTES.add(TOKEN_ATTRIBUTE_EXPIRY);
+        RESERVED_ATTRIBUTES.add(TOKEN_ATTRIBUTE_KEY);
+    }
+
+    private static final Collection<String> RESERVED_PREFIXES = Collections.unmodifiableList(Arrays.asList(
+            NamespaceRegistry.PREFIX_XML,
+            NamespaceRegistry.PREFIX_JCR,
+            NamespaceRegistry.PREFIX_NT,
+            NamespaceRegistry.PREFIX_MIX,
+            Name.NS_XMLNS_PREFIX,
+            Name.NS_REP_PREFIX,
+            Name.NS_SV_PREFIX
+    ));
+
+    private final SessionImpl session;
+    private final UserManager userManager;
+    private final long tokenExpiration;
+
+    TokenProvider(SessionImpl session, long tokenExpiration) throws RepositoryException {
+        this.session = session;
+        this.userManager = session.getUserManager();
+        this.tokenExpiration = tokenExpiration;
+    }
+
+    /**
+     * Create a separate token node underneath a dedicated token store within
+     * the user home node. That token node contains the hashed token, the
+     * expiration time and additional mandatory attributes that will be verified
+     * during login.
+     *
+     * @param user
+     * @param sc The current simple credentials.
+     * @return A new {@code TokenInfo} or {@code null} if the token could not
+     *         be created.
+     */
+    public TokenInfo createToken(User user, SimpleCredentials sc) throws RepositoryException {
+        TokenInfo tokenInfo = null;
+        if (sc != null && user != null && user.getID().equalsIgnoreCase(sc.getUserID())) {
+            String[] attrNames = sc.getAttributeNames();
+            Map<String, String> attributes = new HashMap<String, String>(attrNames.length);
+            for (String attrName : sc.getAttributeNames()) {
+                attributes.put(attrName, sc.getAttribute(attrName).toString());
+            }
+            tokenInfo = createToken(user, attributes);
+            if (tokenInfo != null) {
+                // also set the new token to the simple credentials.
+                sc.setAttribute(TOKEN_ATTRIBUTE, tokenInfo.getToken());
+            }
+        }
+
+        return tokenInfo;
+    }
+
+    /**
+     * Create a separate token node underneath a dedicated token store within
+     * the user home node. That token node contains the hashed token, the
+     * expiration time and additional mandatory attributes that will be verified
+     * during login.
+     *
+     * @param userId     The identifier of the user for which a new token should
+     *                   be created.
+     * @param attributes The attributes associated with the new token.
+     * @return A new {@code TokenInfo} or {@code null} if the token could not
+     *         be created.
+     */
+    private TokenInfo createToken(User user, Map<String, ?> attributes) throws RepositoryException {
+        String error = "Failed to create login token. ";
+        NodeImpl tokenParent = getTokenParent(user);
+        if (tokenParent != null) {
+            try {
+                ValueFactory vf = session.getValueFactory();
+                long creationTime = new Date().getTime();
+                Calendar creation = GregorianCalendar.getInstance();
+                creation.setTimeInMillis(creationTime);
+
+                Name tokenName = session.getQName(Text.replace(ISO8601.format(creation), ":", "."));
+                NodeImpl tokenNode = super.addNode(tokenParent, tokenName, session.getQName(TOKEN_NT_NAME), NodeId.randomId());
+
+                String key = generateKey(8);
+                String token = new StringBuilder(tokenNode.getId().toString()).append(DELIM).append(key).toString();
+
+                String keyHash = PasswordUtility.buildPasswordHash(getKeyValue(key, user.getID()));
+                setProperty(tokenNode, session.getQName(TOKEN_ATTRIBUTE_KEY), vf.createValue(keyHash));
+                setProperty(tokenNode, session.getQName(TOKEN_ATTRIBUTE_EXPIRY), createExpirationValue(creationTime, session));
+
+                for (String name : attributes.keySet()) {
+                    if (!RESERVED_ATTRIBUTES.contains(name)) {
+                        String attr = attributes.get(name).toString();
+                        setProperty(tokenNode, session.getQName(name), vf.createValue(attr));
+                    }
+                }
+                session.save();
+                return new TokenInfoImpl(tokenNode, token, user.getID());
+            } catch (NoSuchAlgorithmException e) {
+                // error while generating login token
+                log.error(error, e);
+            } catch (UnsupportedEncodingException e) {
+                // error while generating login token
+                log.error(error, e);
+            } catch (AccessDeniedException e) {
+                log.warn(error, e);
+            }
+        } else {
+            log.warn("Unable to get/create token store for user {}", user.getID());
+        }
+        return null;
+    }
+
+    private Value createExpirationValue(long creationTime, Session session) throws RepositoryException {
+        Calendar cal = Calendar.getInstance();
+        cal.setTimeInMillis(createExpirationTime(creationTime, tokenExpiration));
+        return session.getValueFactory().createValue(cal);
+    }
+
+    /**
+     * Retrieves the token information associated with the specified login
+     * token. If no accessible {@code Tree} exists for the given token or if
+     * the token is not associated with a valid user this method returns {@code null}.
+     *
+     * @param token A valid login token.
+     * @return The {@code TokenInfo} associated with the specified token or
+     *         {@code null} of the corresponding information does not exist or is not
+     *         associated with a valid user.
+     */
+    public TokenInfo getTokenInfo(String token) throws RepositoryException {
+        if (token == null) {
+            return null;
+        }
+        NodeImpl tokenNode = (NodeImpl) getTokenNode(token, session);
+        String userId = getUserId(tokenNode, userManager);
+        if (userId == null || !isValidTokenTree(tokenNode)) {
+            return null;
+        } else {
+            return new TokenInfoImpl(tokenNode, token, userId);
+        }
+    }
+
+    static Node getTokenNode(String token, Session session) throws RepositoryException {
+        int pos = token.indexOf(DELIM);
+        String id = (pos == -1) ? token : token.substring(0, pos);
+        return session.getNodeByIdentifier(id);
+    }
+
+    static String getUserId(NodeImpl tokenNode, UserManager userManager) throws RepositoryException {
+        if (tokenNode != null) {
+            final NodeImpl userNode = (NodeImpl) tokenNode.getParent().getParent();
+            final String principalName = userNode.getProperty(UserImpl.P_PRINCIPAL_NAME).getString();
+            if (userNode.isNodeType(UserImpl.NT_REP_USER)) {
+                Authorizable a = userManager.getAuthorizable(new ItemBasedPrincipal() {
+                    public String getPath() throws RepositoryException {
+                        return userNode.getPath();
+                    }
+
+                    public String getName() {
+                        return principalName;
+                    }
+                });
+                if (a != null && !a.isGroup() && !((User)a).isDisabled()) {
+                    return a.getID();
+                }
+            } else {
+                throw new RepositoryException("Failed to calculate userId from token credentials");
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns {@code true} if the specified {@code attributeName}
+     * starts with or equals {@link #TOKEN_ATTRIBUTE}.
+     *
+     * @param attributeName The attribute name.
+     * @return {@code true} if the specified {@code attributeName}
+     *         starts with or equals {@link #TOKEN_ATTRIBUTE}.
+     */
+    static boolean isMandatoryAttribute(String attributeName) {
+        return attributeName != null && attributeName.startsWith(TOKEN_ATTRIBUTE);
+    }
+
+    /**
+     * Returns {@code false} if the specified attribute name doesn't have
+     * a 'jcr' or 'rep' namespace prefix; {@code true} otherwise. This is
+     * a lazy evaluation in order to avoid testing the defining node type of
+     * the associated jcr property.
+     *
+     * @param attributeName The attribute name.
+     * @return {@code true} if the specified property name doesn't seem
+     *         to represent repository internal information.
+     */
+    static  boolean isInfoAttribute(String attributeName) {
+        String prefix = Text.getNamespacePrefix(attributeName);
+        return !RESERVED_PREFIXES.contains(prefix);
+    }
+
+    private static long createExpirationTime(long creationTime, long tokenExpiration) {
+        return creationTime + tokenExpiration;
+    }
+
+    private static long getExpirationTime(NodeImpl tokenNode, long defaultValue) throws RepositoryException {
+        if (tokenNode.hasProperty(TOKEN_ATTRIBUTE_EXPIRY)) {
+            return tokenNode.getProperty(TOKEN_ATTRIBUTE_EXPIRY).getLong();
+        } else {
+            return defaultValue;
+        }
+    }
+
+    private static String generateKey(int size) {
+        SecureRandom random = new SecureRandom();
+        byte key[] = new byte[size];
+        random.nextBytes(key);
+
+        StringBuilder res = new StringBuilder(key.length * 2);
+        for (byte b : key) {
+            res.append(Text.hexTable[(b >> 4) & 15]);
+            res.append(Text.hexTable[b & 15]);
+        }
+        return res.toString();
+    }
+
+    private static String getKeyValue(String key, String userId) {
+        return key + userId;
+    }
+
+    private static boolean isValidTokenTree(NodeImpl tokenNode) throws RepositoryException {
+        if (tokenNode == null) {
+            return false;
+        } else {
+            return TOKENS_NODE_NAME.equals(tokenNode.getParent().getName()) &&
+                    TOKEN_NT_NAME.equals(tokenNode.getPrimaryNodeType().getName());
+        }
+    }
+
+    private NodeImpl getTokenParent(User user) throws RepositoryException {
+        NodeImpl tokenParent = null;
+        String parentPath = null;
+        try {
+            if (user != null) {
+                Principal pr = user.getPrincipal();
+                if (pr instanceof ItemBasedPrincipal) {
+                    String userPath = ((ItemBasedPrincipal) pr).getPath();
+                    NodeImpl userNode = (NodeImpl) session.getNode(userPath);
+                    if (userNode.hasNode(TOKENS_NODE_NAME)) {
+                        tokenParent = (NodeImpl) userNode.getNode(TOKENS_NODE_NAME);
+                    } else {
+                        tokenParent = userNode.addNode(session.getQName(TOKENS_NODE_NAME), TOKENS_NT_NAME, NodeId.randomId());
+                        parentPath = userPath + '/' + TOKENS_NODE_NAME;
+                        session.save();
+                    }
+                }
+            } else {
+                log.debug("Cannot create login token: No user specified. (null)");
+            }
+        } catch (RepositoryException e) {
+            // conflict while creating token store for this user -> refresh and
+            // try to get the tree from the updated root.
+            log.debug("Conflict while creating token store -> retrying", e);
+            session.refresh(false);
+            if (parentPath != null && session.nodeExists(parentPath)) {
+                tokenParent = (NodeImpl) session.getNode(parentPath);
+            }
+        }
+        return tokenParent;
+    }
+
+    private class TokenInfoImpl implements TokenInfo {
+
+        private final String token;
+        private final String tokenPath;
+        private final String userId;
+
+        private final long expirationTime;
+        private final String key;
+
+        private final Map<String, String> mandatoryAttributes;
+        private final Map<String, String> publicAttributes;
+
+
+        private TokenInfoImpl(NodeImpl tokenNode, String token, String userId) throws RepositoryException {
+            this.token = token;
+            this.tokenPath = tokenNode.getPath();
+            this.userId = userId;
+
+            expirationTime = getExpirationTime(tokenNode, Long.MIN_VALUE);
+            key = tokenNode.getProperty(TOKEN_ATTRIBUTE_KEY).getString();
+
+            mandatoryAttributes = new HashMap<String, String>();
+            publicAttributes = new HashMap<String, String>();
+            PropertyIterator pit = tokenNode.getProperties();
+            while (pit.hasNext()) {
+                Property property = pit.nextProperty();
+                String name = property.getName();
+                String value = property.getString();
+                if (RESERVED_ATTRIBUTES.contains(name)) {
+                    continue;
+                }
+                if (isMandatoryAttribute(name)) {
+                    mandatoryAttributes.put(name, value);
+                } else if (isInfoAttribute(name)) {
+                    // info attribute
+                    publicAttributes.put(name, value);
+                } // else: jcr specific property
+            }
+        }
+
+        public String getToken() {
+            return token;
+        }
+
+        public boolean isExpired(long loginTime) {
+            return expirationTime < loginTime;
+        }
+
+        public boolean resetExpiration(long loginTime) throws RepositoryException {
+            if (isExpired(loginTime)) {
+                log.debug("Attempt to reset an expired token.");
+                return false;
+            }
+
+            Session s = null;
+            try {
+                if (expirationTime - loginTime <= tokenExpiration / 2) {
+                    s = session.createSession(session.getWorkspace().getName());
+                    setProperty((NodeImpl) s.getNode(tokenPath), session.getQName(TOKEN_ATTRIBUTE_EXPIRY), createExpirationValue(loginTime, session));
+                    s.save();
+                    log.debug("Successfully reset token expiration time.");
+                    return true;
+                }
+            } catch (RepositoryException e) {
+                log.warn("Error while resetting token expiration", e);
+            } finally {
+                if (s != null) {
+                    s.logout();
+                }
+            }
+            return false;
+        }
+
+        public boolean matches(TokenCredentials tokenCredentials) {
+            String tk = tokenCredentials.getToken();
+            int pos = tk.lastIndexOf(DELIM);
+            if (pos > -1) {
+                tk = tk.substring(pos + 1);
+            }
+            if (key == null || !PasswordUtility.isSame(key, getKeyValue(tk, userId))) {
+                return false;
+            }
+
+            for (String name : mandatoryAttributes.keySet()) {
+                String expectedValue = mandatoryAttributes.get(name);
+                if (!expectedValue.equals(tokenCredentials.getAttribute(name))) {
+                    return false;
+                }
+            }
+
+            // update set of informative attributes on the credentials
+            // based on the properties present on the token node.
+            Collection<String> attrNames = Arrays.asList(tokenCredentials.getAttributeNames());
+            for (String name : publicAttributes.keySet()) {
+                if (!attrNames.contains(name)) {
+                    tokenCredentials.setAttribute(name, publicAttributes.get(name).toString());
+
+                }
+            }
+            return true;
+        }
+
+        public boolean remove() {
+            Session s = null;
+            try {
+                s = session.createSession(session.getWorkspace().getName());
+                Node node = s.getNode(tokenPath);
+                node.remove();
+                s.save();
+                return true;
+            } catch (RepositoryException e) {
+                log.warn("Internal error while removing token node.", e);
+            } finally {
+                if (s != null) {
+                    s.logout();
+                }
+            }
+            return false;
+        }
+
+        public TokenCredentials getCredentials() {
+            TokenCredentials tc = new TokenCredentials(token);
+            for (String name : mandatoryAttributes.keySet()) {
+                tc.setAttribute(name, mandatoryAttributes.get(name));
+            }
+            for (String name : publicAttributes.keySet()) {
+                tc.setAttribute(name, publicAttributes.get(name));
+            }
+            return tc;
+        }
+
+    }
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AbstractACLTemplate.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AbstractACLTemplate.java
index 1d17bc2..49e14c6 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AbstractACLTemplate.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AbstractACLTemplate.java
@@ -98,6 +98,17 @@ public abstract class AbstractACLTemplate implements JackrabbitAccessControlList
     }
 
     /**
+     * @see org.apache.jackrabbit.api.security.JackrabbitAccessControlList#addEntry(Principal, Privilege[], boolean, Map, Map)
+     */
+    public boolean addEntry(Principal principal, Privilege[] privileges, boolean isAllow, Map<String, Value> restrictions, Map<String, Value[]> mvRestrictions) throws AccessControlException, RepositoryException {
+        if (mvRestrictions == null || mvRestrictions.isEmpty()) {
+            return addEntry(principal, privileges, isAllow, restrictions);
+        } else {
+            throw new UnsupportedRepositoryOperationException("Not implemented. Please use Jackrabbit OAK to get support for multi-valued restrictions.");
+        }
+    }
+
+    /**
      * @see org.apache.jackrabbit.api.security.JackrabbitAccessControlList#size()
      */
     public int size() {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlEditor.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlEditor.java
index cd81861..5867da6 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlEditor.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlEditor.java
@@ -39,7 +39,7 @@ public interface AccessControlEditor {
      * the <code>AccessControlProvider</code> and are only an external representation.
      * Modification will therefore not take effect, until they are written back to
      * the editor and persisted.
-     * <p/>
+     * <p>
      * Compared to the policy returned by {@link AccessControlProvider#getEffectivePolicies(org.apache.jackrabbit.spi.Path, CompiledPermissions)},
      * the scope of the policies it limited to the Node itself and does
      * not take inherited elements into account.
@@ -85,11 +85,11 @@ public interface AccessControlEditor {
      * <code>AccessControlProvider</code> and is only an external
      * representation. Modification will therefore not take effect, until a
      * modified policy is written back to the editor and persisted.
-     * <p/>
+     * <p>
      * See {@link #getPolicies(String)} for the corresponding method that returns
      * the editable policies that have been set to the node at
      * <code>nodePath</code> before.
-     * <p/>
+     * <p>
      * Compared to the policies returned by {@link AccessControlProvider#getEffectivePolicies(org.apache.jackrabbit.spi.Path, CompiledPermissions)},
      * the scope of the policies returned by this methods it limited to the Node
      * itself and does never not take inherited elements into account.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlEntryImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlEntryImpl.java
index 35da8a2..2bac64b 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlEntryImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlEntryImpl.java
@@ -23,6 +23,7 @@ import org.apache.jackrabbit.value.ValueHelper;
 
 import javax.jcr.NamespaceException;
 import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.Value;
 import javax.jcr.ValueFactory;
 import javax.jcr.security.AccessControlException;
@@ -299,6 +300,13 @@ public abstract class AccessControlEntryImpl implements JackrabbitAccessControlE
         return getRestriction(getResolver().getQName(restrictionName));
     }
 
+    /**
+     * @see org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry#getRestrictions(String)
+     */
+    public Value[] getRestrictions(String restrictionName) throws RepositoryException {
+        return new Value[] {getRestriction(restrictionName)};
+    }
+
     //-------------------------------------------------------------< Object >---
     /**
      * @see Object#hashCode()
@@ -328,4 +336,4 @@ public abstract class AccessControlEntryImpl implements JackrabbitAccessControlE
         }
         return false;
     }
-}
\ No newline at end of file
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlProvider.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlProvider.java
index 857faf9..8d6b653 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlProvider.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlProvider.java
@@ -33,7 +33,7 @@ import java.util.Set;
  * objects that apply to an item in a single workspace. The provider is bound
  * to a system session in contrast to the <code>AccessControlManager</code> that
  * is bound to a specific session/subject.
- * <p/>
+ * <p>
  * Please note following additional special conditions:
  * <ul>
  * <li>The detection of access control policy/entries is an implementation
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlProviderFactory.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlProviderFactory.java
index 1bfa743..4c190a6 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlProviderFactory.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/AccessControlProviderFactory.java
@@ -26,12 +26,12 @@ import javax.jcr.Session;
  * {@link AccessControlProvider}s for the various workspaces present in the
  * repository. If a provider is no longer used by the workspace, it is
  * {@link AccessControlProvider#close() closed}.
- * <p/>
+ * <p>
  * The factory does not need to cache the created {@link AccessControlProvider}s.
  * They are used during the entire lifetime of their workspace, and are cached
  * together with the respective workspace related objects by the repository
  * implementation.
- * <p/>
+ * <p>
  * The {@link AccessControlProvider}s are requested using a
  * {@link Session system Session}. The system sessions have a distinct access
  * control rules in order to prevent chicken-egg problems when setting up
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/GlobPattern.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/GlobPattern.java
index e719531..07a1d86 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/GlobPattern.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/GlobPattern.java
@@ -16,20 +16,20 @@
  */
 package org.apache.jackrabbit.core.security.authorization;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.jackrabbit.util.Text;
-
 import javax.jcr.Item;
 import javax.jcr.RepositoryException;
 
+import org.apache.jackrabbit.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 /**
  * <code>GlobPattern</code> defines a simplistic pattern matching. It consists
  * of a mandatory (leading) path and an optional "glob" that may contain one or
  * more wildcard characters ("<code>*</code>") according to the glob matching
  * defined by {@link javax.jcr.Node#getNodes(String[])}. In contrast to that
  * method the <code>GlobPattern</code> operates on path (not only names).
- * <p/>
+ * <p>
  *
  * <p>
  * Please note the following special cases:
@@ -64,7 +64,7 @@ public final class GlobPattern {
     private static Logger log = LoggerFactory.getLogger(GlobPattern.class);
 
     private static final char WILDCARD_CHAR = '*';
-    private static final String WILDCARD_STRING = "*";
+    private static final int  MAX_WILDCARD = 20;
 
     private final String nodePath;
     private final String restriction;
@@ -121,7 +121,7 @@ public final class GlobPattern {
             // TODO: missing proper impl
             return matches(itemToMatch.getPath());
         } catch (RepositoryException e) {
-            log.error("Unable to determine match.", e.getMessage());
+            log.error("Unable to determine match. {}", e.getMessage());
             return false;
         }
     }
@@ -220,7 +220,7 @@ public final class GlobPattern {
             }
             char[] tm = (toMatch.endsWith("/")) ? toMatch.substring(0, toMatch.length()-1).toCharArray() : toMatch.toCharArray();
             // shortcut didn't reveal mismatch -> need to process the internal match method.
-            return matches(patternChars, 0, tm, 0);
+            return matches(patternChars, 0, tm, 0, MAX_WILDCARD);
         }
 
         /**
@@ -232,8 +232,11 @@ public final class GlobPattern {
          * @return <code>true</code> if matches, <code>false</code> otherwise
          */
         private boolean matches(char[] pattern, int pOff,
-                                char[] s, int sOff) {
+                                char[] s, int sOff, int cnt) {
 
+            if (cnt <= 0) {
+                throw new IllegalArgumentException("Illegal glob pattern " + GlobPattern.this);
+            }
             /*
             NOTE: code has been copied (and slightly modified) from
             ChildrenCollectorFilter#internalMatches.
@@ -263,8 +266,9 @@ public final class GlobPattern {
                         return true;
                     }
 
+                    cnt--;
                     while (true) {
-                        if (matches(pattern, pOff, s, sOff)) {
+                        if (matches(pattern, pOff, s, sOff, cnt)) {
                             return true;
                         }
                         if (sOff >= sLength) {
@@ -287,4 +291,4 @@ public final class GlobPattern {
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/PrivilegeManagerImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/PrivilegeManagerImpl.java
index 2bc07fa..856dcd2 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/PrivilegeManagerImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/PrivilegeManagerImpl.java
@@ -77,6 +77,13 @@ public final class PrivilegeManagerImpl implements PrivilegeManager, PrivilegeRe
         registry.addListener(this);
     }
 
+    /**
+     * Disposes this privilege manager
+     */
+    public void dispose() {
+        registry.removeListener(this);
+    }
+
     //---------------------------------------------------< PrivilegeManager >---
     /**
      * @see PrivilegeManager#getRegisteredPrivileges()
@@ -262,7 +269,7 @@ public final class PrivilegeManagerImpl implements PrivilegeManager, PrivilegeRe
 
     //-----------------------------------------< PrivilegeRegistry.Listener >---
     /**
-     * @see PrivilegeRegistry.Listener#privilegesRegistered(java.util.Set
+     * @see PrivilegeRegistry.Listener#privilegesRegistered(java.util.Set)
      * @param privilegeNames
      */
     public void privilegesRegistered(Set<Name> privilegeNames) {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/PrivilegeRegistry.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/PrivilegeRegistry.java
index 6366f19..de578e9 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/PrivilegeRegistry.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/PrivilegeRegistry.java
@@ -377,13 +377,6 @@ public final class PrivilegeRegistry implements PrivilegeEventListener {
                 perm |= Permission.ADD_NODE;
             }
 
-            // modify_child_node_collection permission is granted through
-            // privileges on the parent
-            if ((parentPrivs & ADD_CHILD_NODES) == ADD_CHILD_NODES &&
-                    (parentPrivs & REMOVE_CHILD_NODES) == REMOVE_CHILD_NODES) {
-                perm |= Permission.MODIFY_CHILD_NODE_COLLECTION;
-            }
-
             /*
              remove_node is
              allowed: only if remove_child_nodes privilege is present on
@@ -404,6 +397,13 @@ public final class PrivilegeRegistry implements PrivilegeEventListener {
             }
         }
 
+        // modify_child_node_collection permission is granted through
+        // privileges on the parent
+        if ((parentPrivs & ADD_CHILD_NODES) == ADD_CHILD_NODES &&
+                (parentPrivs & REMOVE_CHILD_NODES) == REMOVE_CHILD_NODES) {
+            perm |= Permission.MODIFY_CHILD_NODE_COLLECTION;
+        }
+
         // the remaining (special) permissions are simply defined on the node
         if ((privs & READ_AC) == READ_AC) {
             perm |= Permission.READ_AC;
@@ -665,6 +665,15 @@ public final class PrivilegeRegistry implements PrivilegeEventListener {
         listeners.put(listener,listener);
     }
 
+    /**
+     * Removes a privilege registration listener.
+     *
+     * @param listener
+     */
+    public void removeListener(Listener listener) {
+        listeners.remove(listener);
+    }
+
     //---------------------------------------------< privilege registration >---
     /**
      * Register the specified custom privilege definitions.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/UnmodifiableAccessControlList.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/UnmodifiableAccessControlList.java
index 473e21e..32a1e29 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/UnmodifiableAccessControlList.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/UnmodifiableAccessControlList.java
@@ -171,6 +171,10 @@ public class UnmodifiableAccessControlList implements JackrabbitAccessControlLis
         throw new AccessControlException("Unmodifiable ACL. Use AccessControlManager#getPolicy or #getApplicablePolicies in order to obtain an modifiable ACL.");
     }
 
+    public boolean addEntry(Principal principal, Privilege[] privileges, boolean isAllow, Map<String, Value> restrictions, Map<String, Value[]> mvRestrictions) throws AccessControlException, RepositoryException {
+        throw new AccessControlException("Unmodifiable ACL. Use AccessControlManager#getPolicy or #getApplicablePolicies in order to obtain an modifiable ACL.");
+    }
+
     /**
      * @see org.apache.jackrabbit.api.security.JackrabbitAccessControlList#orderBefore(AccessControlEntry, AccessControlEntry)
      */
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLEditor.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLEditor.java
index 4542efe..f5c11fb 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLEditor.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLEditor.java
@@ -68,8 +68,9 @@ public class ACLEditor extends ProtectedItemModifier implements AccessControlEdi
      */
     private final SessionImpl session;
     private final AccessControlUtils utils;
+    private final boolean allowUnknownPrincipals;
 
-    ACLEditor(Session editingSession, AccessControlUtils utils) {
+    ACLEditor(Session editingSession, AccessControlUtils utils, boolean allowUnknownPrincipals) {
         super(Permission.MODIFY_AC);
         if (editingSession instanceof SessionImpl) {
             session = ((SessionImpl) editingSession);
@@ -77,6 +78,7 @@ public class ACLEditor extends ProtectedItemModifier implements AccessControlEdi
             throw new IllegalArgumentException("org.apache.jackrabbit.core.SessionImpl expected. Found " + editingSession.getClass());
         }
         this.utils = utils;
+        this.allowUnknownPrincipals = allowUnknownPrincipals;
     }
 
     /**
@@ -87,7 +89,7 @@ public class ACLEditor extends ProtectedItemModifier implements AccessControlEdi
      * @throws RepositoryException if an error occurs
      */
     ACLTemplate getACL(NodeImpl aclNode, String path) throws RepositoryException {
-        return new ACLTemplate(aclNode, path);
+        return new ACLTemplate(aclNode, path, allowUnknownPrincipals);
     }
 
     //------------------------------------------------< AccessControlEditor >---
@@ -151,7 +153,7 @@ public class ACLEditor extends ProtectedItemModifier implements AccessControlEdi
                 PrivilegeManager privMgr = ((JackrabbitWorkspace) session.getWorkspace()).getPrivilegeManager();
                 if (controlledNode.isNodeType(mixin) || controlledNode.canAddMixin(mixin)) {
                     acl = new ACLTemplate(nodePath, session.getPrincipalManager(),
-                            privMgr, session.getValueFactory(), session);
+                            privMgr, session.getValueFactory(), session, allowUnknownPrincipals);
                 } else {
                     log.warn("Node {} cannot be made access controllable.", nodePath);
                 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLProvider.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLProvider.java
index 069cd57..c6c9382 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLProvider.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLProvider.java
@@ -43,14 +43,12 @@ import javax.jcr.Session;
 import javax.jcr.query.Query;
 import javax.jcr.query.QueryManager;
 import javax.jcr.query.QueryResult;
-import javax.jcr.security.AccessControlEntry;
 import javax.jcr.security.AccessControlList;
 import javax.jcr.security.AccessControlManager;
 import javax.jcr.security.AccessControlPolicy;
 import javax.jcr.security.Privilege;
 import java.security.Principal;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -80,6 +78,17 @@ import java.util.Set;
 public class ACLProvider extends AbstractAccessControlProvider implements AccessControlConstants {
 
     /**
+     * Constant for the name of the configuration option {@code allow-unknown-principals}.
+     * The option is a flag indicating whether access control entries with principals not known to the system
+     * can be added to an ACL. the default is {@code false}.
+     * <p>
+     * Please note that the current implementation does only check principal existence when adding a new access
+     * control entry, but does not validate all ACEs when removing a principal. So even if this flag is {@code false},
+     * it's possible to create an ACL with a unknown principal.
+     */
+    public static final String PARAM_ALLOW_UNKNOWN_PRINCIPALS = "allow-unknown-principals";
+
+    /**
      * the default logger
      */
     private static final Logger log = LoggerFactory.getLogger(ACLProvider.class);
@@ -96,6 +105,11 @@ public class ACLProvider extends AbstractAccessControlProvider implements Access
      */
     private EntryCollector entryCollector;
 
+    /**
+     * controls if unknown principals are allowed in ACLs
+     */
+    private boolean allowUnknownPrincipals;
+
     //----------------------------------------------< AccessControlProvider >---
     /**
      * @see org.apache.jackrabbit.core.security.authorization.AccessControlProvider#init(Session, Map)
@@ -103,12 +117,13 @@ public class ACLProvider extends AbstractAccessControlProvider implements Access
     @Override
     public void init(Session systemSession, Map configuration) throws RepositoryException {
         super.init(systemSession, configuration);
+        allowUnknownPrincipals = "true".equals(configuration.get(PARAM_ALLOW_UNKNOWN_PRINCIPALS));
 
         // make sure the workspace of the given systemSession has a
         // minimal protection on the root node.
         NodeImpl root = (NodeImpl) session.getRootNode();
         rootNodeId = root.getNodeId();
-        ACLEditor systemEditor = new ACLEditor(session, this);
+        ACLEditor systemEditor = new ACLEditor(session, this, allowUnknownPrincipals);
 
         // TODO: replace by configurable default policy (see JCR-2331)
         boolean initializedWithDefaults = !configuration.containsKey(PARAM_OMIT_DEFAULT_PERMISSIONS);
@@ -137,9 +152,7 @@ public class ACLProvider extends AbstractAccessControlProvider implements Access
             targetNode = (NodeImpl) session.getRootNode();
             if (isRepoAccessControlled(targetNode)) {
                 if (permissions.grants(targetNode.getPrimaryPath(), Permission.READ_AC)) {
-                    // retrieve the entries for the access controlled node
-                    List<AccessControlEntry> entries = entryCollector.collectEntries(null, new EntryFilterImpl(null, (NodeId) null, session));
-                    acls.add(new UnmodifiableAccessControlList(entries));
+                    acls.add(getACL(targetNode, N_REPO_POLICY, null));
                 } else {
                     throw new AccessDeniedException("Access denied at " + targetNode.getPath());
                 }
@@ -192,7 +205,7 @@ public class ACLProvider extends AbstractAccessControlProvider implements Access
             Query q = qm.createQuery(stmt.toString(), Query.XPATH);
             result = q.execute();
         } catch (RepositoryException e) {
-            log.error("Unexpected error while searching effective policies.", e.getMessage());            
+            log.error("Unexpected error while searching effective policies. {}", e.getMessage());            
             throw new UnsupportedOperationException("Retrieve effective policies for set of principals not supported.", e);
         }
 
@@ -204,15 +217,13 @@ public class ACLProvider extends AbstractAccessControlProvider implements Access
 
             if (N_POLICY.equals(aclName) && isAccessControlled(accessControlledNode)) {
                 if (permissions.canRead(aclNode.getPrimaryPath(), aclNode.getNodeId())) {
-                    List<AccessControlEntry> aces = entryCollector.getEntries(accessControlledNode).getACEs();
-                    acls.add(new UnmodifiableAccessControlList(aces, accessControlledNode.getPath(), Collections.<String, Integer>emptyMap()));
+                    acls.add(getACL(accessControlledNode, N_POLICY, accessControlledNode.getPath()));
                 } else {
                     throw new AccessDeniedException("Access denied at " + Text.getRelativeParent(aclNode.getPath(), 1));
                 }
             } else if (N_REPO_POLICY.equals(aclName) && isRepoAccessControlled(accessControlledNode)) {
                 if (permissions.canRead(aclNode.getPrimaryPath(), aclNode.getNodeId())) {
-                    List<AccessControlEntry> aces = entryCollector.collectEntries(null, new EntryFilterImpl(null, (NodeId) null, session));
-                    acls.add(new UnmodifiableAccessControlList(aces));
+                    acls.add(getACL(accessControlledNode, N_REPO_POLICY, null));
                 } else {
                     throw new AccessDeniedException("Access denied at " + Text.getRelativeParent(aclNode.getPath(), 1));
                 }
@@ -227,7 +238,7 @@ public class ACLProvider extends AbstractAccessControlProvider implements Access
      */
     public AccessControlEditor getEditor(Session session) {
         checkInitialized();
-        return new ACLEditor(session, this);
+        return new ACLEditor(session, this, allowUnknownPrincipals);
     }
 
     /**
@@ -290,9 +301,7 @@ public class ACLProvider extends AbstractAccessControlProvider implements Access
         // it to the list
         if (isAccessControlled(node)) {
             if (permissions.grants(node.getPrimaryPath(), Permission.READ_AC)) {
-                // retrieve the entries for the access controlled node
-                List<AccessControlEntry> aces = entryCollector.getEntries(node).getACEs();
-                acls.add(new UnmodifiableAccessControlList(aces, node.getPath(), Collections.<String, Integer>emptyMap()));
+                acls.add(getACL(node, N_POLICY, node.getPath()));
             } else {
                 throw new AccessDeniedException("Access denied at " + node.getPath());
             }
@@ -304,6 +313,14 @@ public class ACLProvider extends AbstractAccessControlProvider implements Access
         }
     }
 
+    private AccessControlList getACL(NodeImpl accessControlledNode, Name policyName, String path) throws RepositoryException {
+        // collect the aces of that node.
+        NodeImpl aclNode = accessControlledNode.getNode(policyName);
+        AccessControlList acl = new ACLTemplate(aclNode, path, allowUnknownPrincipals);
+
+        return new UnmodifiableAccessControlList(acl);
+    }
+
     /**
      * Set-up minimal permissions for the workspace:
      *
@@ -354,19 +371,15 @@ public class ACLProvider extends AbstractAccessControlProvider implements Access
     }
 
     /**
-     * Test if the given node is access controlled. The node is access
-     * controlled if it is of node type
-     * {@link AccessControlConstants#NT_REP_ACCESS_CONTROLLABLE "rep:AccessControllable"}
-     * and if it has a child node named
-     * {@link AccessControlConstants#N_POLICY}.
+     * Test if the given node is access controlled.
      *
      * @param node the node to be tested
-     * @return <code>true</code> if the node is access controlled and has a
-     * rep:policy child; <code>false</code> otherwise.
+     * @return <code>true</code> if the node is access controlled.
      * @throws RepositoryException if an error occurs
+     * @see org.apache.jackrabbit.core.NodeImpl#isAccessControllable()
      */
     static boolean isAccessControlled(NodeImpl node) throws RepositoryException {
-        return node.hasNode(N_POLICY) && node.isNodeType(NT_REP_ACCESS_CONTROLLABLE);
+        return node.isAccessControllable();
     }
 
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplate.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplate.java
index 15fb46e..02677ed 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplate.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplate.java
@@ -21,7 +21,6 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 import javax.jcr.NamespaceException;
 import javax.jcr.NodeIterator;
@@ -31,7 +30,6 @@ import javax.jcr.Value;
 import javax.jcr.ValueFactory;
 import javax.jcr.security.AccessControlEntry;
 import javax.jcr.security.AccessControlException;
-import javax.jcr.security.AccessControlManager;
 import javax.jcr.security.Privilege;
 
 import org.apache.jackrabbit.api.JackrabbitWorkspace;
@@ -40,13 +38,10 @@ import org.apache.jackrabbit.api.security.authorization.PrivilegeManager;
 import org.apache.jackrabbit.api.security.principal.PrincipalManager;
 import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.SessionImpl;
-import org.apache.jackrabbit.core.id.NodeId;
-import org.apache.jackrabbit.core.nodetype.NodeTypeImpl;
 import org.apache.jackrabbit.core.security.authorization.AbstractACLTemplate;
 import org.apache.jackrabbit.core.security.authorization.AccessControlEntryImpl;
 import org.apache.jackrabbit.core.security.authorization.PrivilegeBits;
 import org.apache.jackrabbit.core.security.authorization.PrivilegeManagerImpl;
-import org.apache.jackrabbit.core.security.authorization.GlobPattern;
 import org.apache.jackrabbit.core.security.principal.PrincipalImpl;
 import org.apache.jackrabbit.core.security.principal.UnknownPrincipal;
 import org.apache.jackrabbit.core.value.InternalValue;
@@ -90,17 +85,14 @@ class ACLTemplate extends AbstractACLTemplate {
     private final NameResolver resolver;
 
     /**
-     * The id of the access controlled node or <code>null</code> if this
-     * ACLTemplate isn't created for an existing access controlled node.
-     * Used for the Entry#isLocal(NodeId) call only in order to avoid calls
-     * to {@link javax.jcr.Node#getPath()}.
+     * Namespace sensitive name of the REP_GLOB property in standard JCR form.
      */
-    private final NodeId id;
+    private final String jcrRepGlob;
 
     /**
-     *
+     * controls if unknown principals can be used in access control entries.
      */
-    private final String jcrRepGlob;
+    private final boolean allowUnknownPrincipals;
 
     /**
      * Construct a new empty {@link ACLTemplate}.
@@ -114,12 +106,12 @@ class ACLTemplate extends AbstractACLTemplate {
      */
     ACLTemplate(String path, PrincipalManager principalMgr, 
                 PrivilegeManager privilegeMgr, ValueFactory valueFactory,
-                NamePathResolver resolver) throws NamespaceException {
+                NamePathResolver resolver, boolean allowUnknownPrincipals) throws NamespaceException {
         super(path, valueFactory);
         this.principalMgr = principalMgr;
         this.privilegeMgr = (PrivilegeManagerImpl) privilegeMgr;
         this.resolver = resolver;
-        this.id = null;
+        this.allowUnknownPrincipals = allowUnknownPrincipals;
 
         jcrRepGlob = resolver.getJCRName(P_GLOB);
     }
@@ -129,35 +121,23 @@ class ACLTemplate extends AbstractACLTemplate {
      * node.
      *
      * @param aclNode node
+     * @param path The path as exposed by {@link JackrabbitAccessControlList#getPath()}
      * @throws RepositoryException if an error occurs
      */
-    ACLTemplate(NodeImpl aclNode) throws RepositoryException {
-        this(aclNode, ((aclNode != null) ? aclNode.getParent().getPath() : null));
-    }
-
-    /**
-     * Create a {@link ACLTemplate} that is used to edit an existing ACL
-     * node.
-     *
-     * @param aclNode node
-     * @param path The path as exposed by "@link JackrabbitAccessControlList#getPath()}
-     * @throws RepositoryException if an error occurs
-     */
-    ACLTemplate(NodeImpl aclNode, String path) throws RepositoryException {
+    ACLTemplate(NodeImpl aclNode, String path, boolean allowUnknownPrincipals) throws RepositoryException {
         super(path, (aclNode != null) ? aclNode.getSession().getValueFactory() : null);
-        if (aclNode == null || !NT_REP_ACL.equals(((NodeTypeImpl)aclNode.getPrimaryNodeType()).getQName())) {
+        if (aclNode == null || !NT_REP_ACL.equals(aclNode.getPrimaryNodeTypeName())) {
             throw new IllegalArgumentException("Node must be of type 'rep:ACL'");
         }
         SessionImpl sImpl = (SessionImpl) aclNode.getSession();
         principalMgr = sImpl.getPrincipalManager();
         privilegeMgr = (PrivilegeManagerImpl) ((JackrabbitWorkspace) sImpl.getWorkspace()).getPrivilegeManager();
+        this.allowUnknownPrincipals = allowUnknownPrincipals;
 
         this.resolver = sImpl;
-        this.id = aclNode.getParentId();
         jcrRepGlob = sImpl.getJCRName(P_GLOB);
 
         // load the entries:
-        AccessControlManager acMgr = sImpl.getAccessControlManager();
         NodeIterator itr = aclNode.getNodes();
         while (itr.hasNext()) {
             NodeImpl aceNode = (NodeImpl) itr.nextNode();
@@ -180,13 +160,12 @@ class ACLTemplate extends AbstractACLTemplate {
                     restrictions = Collections.singletonMap(jcrRepGlob, aceNode.getProperty(P_GLOB).getValue());
                 }
                 // create a new ACEImpl (omitting validation check)
-                Entry ace = new Entry(princ, privilegeMgr.getBits(privNames),
-                        NT_REP_GRANT_ACE.equals(((NodeTypeImpl) aceNode.getPrimaryNodeType()).getQName()),
-                        restrictions);
+                boolean isAllow = NT_REP_GRANT_ACE.equals(aceNode.getPrimaryNodeTypeName());
+                Entry ace = new Entry(princ, privilegeMgr.getBits(privNames), isAllow, restrictions);
                 // add the entry omitting any validation.
                 entries.add(ace);
             } catch (RepositoryException e) {
-                log.debug("Failed to build ACE from content.", e.getMessage());
+                log.debug("Failed to build ACE from content. {}", e.getMessage());
             }
         }
     }
@@ -321,7 +300,10 @@ class ACLTemplate extends AbstractACLTemplate {
         if (principal instanceof UnknownPrincipal) {
             log.debug("Consider fallback principal as valid: {}", principal.getName());
         } else if (!principalMgr.hasPrincipal(principal.getName())) {
-            throw new AccessControlException("Principal " + principal.getName() + " does not exist.");
+            if (!allowUnknownPrincipals) {
+                throw new AccessControlException("Principal " + principal.getName() + " does not exist.");
+            }
+            log.debug("Consider fallback principal as valid: {}", principal.getName());
         }
 
         if (path == null && restrictions != null && !restrictions.isEmpty()) {
@@ -427,61 +409,22 @@ class ACLTemplate extends AbstractACLTemplate {
      */
     class Entry extends AccessControlEntryImpl {
 
-        private final GlobPattern pattern;
-
         private Entry(Principal principal, PrivilegeBits privilegeBits, boolean allow, Map<String,Value> restrictions)
                 throws RepositoryException {
             super(principal, privilegeBits, allow, restrictions);
-            pattern = calculatePattern();
         }
 
         private Entry(Principal principal, Privilege[] privileges, boolean allow, Map<String,Value> restrictions)
                 throws RepositoryException {
             super(principal, privileges, allow, restrictions);
-            pattern = calculatePattern();
         }
 
         private Entry(Entry base, PrivilegeBits newPrivilegeBits, boolean isAllow) throws RepositoryException {
             super(base, newPrivilegeBits, isAllow);
-            pattern = calculatePattern();
         }
 
         private Entry(Entry base, Privilege[] newPrivileges, boolean isAllow) throws RepositoryException {
             super(base, newPrivileges, isAllow);
-            pattern = calculatePattern();
-        }
-
-        private GlobPattern calculatePattern() throws RepositoryException {
-            if (path == null) {
-                return null; // no pattern for repo-level aces.
-            } else {
-                GlobPattern p;
-                Value glob = getRestrictions().get(P_GLOB);
-                if (glob != null) {
-                    p = GlobPattern.create(path, glob.getString());
-                } else {
-                    p = GlobPattern.create(path);
-                }
-                return p;
-            }
-        }
-        
-        /**
-         * @param nodeId
-         * @return <code>true</code> if this entry is defined on the node
-         * at <code>nodeId</code>
-         */
-        boolean isLocal(NodeId nodeId) {
-            return id != null && id.equals(nodeId);
-        }
-
-        /**
-         * 
-         * @param jcrPath
-         * @return
-         */
-        boolean matches(String jcrPath) {
-            return pattern != null && pattern.matches(jcrPath);
         }
 
         @Override
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CachingEntryCollector.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CachingEntryCollector.java
index e1564ee..351550f 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CachingEntryCollector.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CachingEntryCollector.java
@@ -16,6 +16,12 @@
  */
 package org.apache.jackrabbit.core.security.authorization.acl;
 
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.jcr.RepositoryException;
+
 import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.cache.GrowingLRUMap;
@@ -24,9 +30,6 @@ import org.apache.jackrabbit.core.security.authorization.AccessControlModificati
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.jcr.RepositoryException;
-import java.util.Map;
-
 /**
  * <code>CachingEntryCollector</code> extends <code>EntryCollector</code> by
  * keeping a cache of ACEs per access controlled nodeId.
@@ -43,8 +46,11 @@ class CachingEntryCollector extends EntryCollector {
      * nodeID (key). The map only contains an entry if the corresponding Node
      * is access controlled.
      */
-    private final Map<NodeId, Entries> cache;
-    private final Object monitor = new Object();
+    private final EntryCache cache;
+
+    private ConcurrentMap<NodeId, FutureEntries> futures = new ConcurrentHashMap<NodeId, FutureEntries>();
+    private final String strategy;
+    private final boolean cacheNoAcl;
 
     /**
      * Create a new instance.
@@ -53,52 +59,56 @@ class CachingEntryCollector extends EntryCollector {
      * @param rootID The id of the root node.
      * @throws RepositoryException If an error occurs.
      */
-    @SuppressWarnings("unchecked")    
     CachingEntryCollector(SessionImpl systemSession, NodeId rootID) throws RepositoryException {
         super(systemSession, rootID);
+        cache = new EntryCache();
 
-        cache = new GrowingLRUMap(1024, 5000);
+        // for testing purposes, see JCR-2950
+        String propname = "org.apache.jackrabbit.core.security.authorization.acl.CachingEntryCollector.strategy";
+        strategy = System.getProperty(propname, "T");
+        if (!("S".equals(strategy) || "T".equals(strategy) || "P".equals(strategy))) {
+            throw new RepositoryException("Invalid value " + strategy + " specified for system property " + propname);
+        }
+
+        log.info("Cache Update Strategy: " + strategy);
+
+        propname = "org.apache.jackrabbit.core.security.authorization.acl.CachingEntryCollector.cacheNoACL";
+        cacheNoAcl = Boolean.parseBoolean(System.getProperty(propname, "false"));
+
+        log.info("Caching entries with no ACLs: " + cacheNoAcl);
     }
 
     @Override
     protected void close() {
         super.close();
-        synchronized (monitor) {
-            cache.clear();
-        }
+        cache.clear();
     }
 
     //-----------------------------------------------------< EntryCollector >---
     /**
      * @see EntryCollector#getEntries(org.apache.jackrabbit.core.NodeImpl)
      */
-    @Override    
+    @Override
     protected Entries getEntries(NodeImpl node) throws RepositoryException {
-        Entries entries;
         NodeId nodeId = node.getNodeId();
-        synchronized (monitor) {
-            entries = cache.get(nodeId);
-            if (entries == null) {
-                // fetch entries and update the cache
-                entries = updateCache(node);
-            }
+        Entries entries = cache.get(nodeId);
+        if (entries == null) {
+            // fetch entries and update the cache
+            entries = updateCache(node);
         }
         return entries;
     }
-    
+
     /**
      * @see EntryCollector#getEntries(org.apache.jackrabbit.core.id.NodeId)
      */
     @Override
     protected Entries getEntries(NodeId nodeId) throws RepositoryException {
-        Entries entries;
-        synchronized (monitor) {
-            entries = cache.get(nodeId);
-            if (entries == null) {
-                // fetch entries and update the cache
-                NodeImpl n = getNodeById(nodeId);
-                entries = updateCache(n);
-            }
+        Entries entries = cache.get(nodeId);
+        if (entries == null) {
+            // fetch entries and update the cache
+            NodeImpl n = getNodeById(nodeId);
+            entries = updateCache(n);
         }
         return entries;
     }
@@ -111,40 +121,131 @@ class CachingEntryCollector extends EntryCollector {
      * @return The list of entries present on the specified node or an empty list.
      * @throws RepositoryException If an error occurs.
      */
-    private Entries updateCache(NodeImpl node) throws RepositoryException {
+    private Entries internalUpdateCache(NodeImpl node) throws RepositoryException {
         Entries entries = super.getEntries(node);
-        if (!entries.isEmpty()) {
-            // find the next access control ancestor in the hierarchy
-            // 'null' indicates that there is no ac-controlled ancestor.
-            NodeId nextId = null;
-            NodeImpl n = node;            
-            while (nextId == null && !rootID.equals(n.getNodeId())) {
-                if (cache.containsKey(n.getNodeId())) {
-                    nextId = n.getNodeId();
-                } else if (cache.containsKey(n.getParentId())) {
-                    nextId = n.getParentId();
-                } else {
-                    n = (NodeImpl) n.getParent();
-                    if (hasEntries(n)) {
-                        nextId = n.getNodeId();
-                    } // else: not access controlled -> test next ancestors
-                }
-            }
-
+        if (cacheNoAcl || (isRootId(node.getNodeId()) && cache.specialCasesRoot()) || !entries.isEmpty()) {
             // adjust the 'nextId' to point to the next access controlled
             // ancestor node instead of the parent and remember the entries.
-            entries.setNextId(nextId);
+            entries.setNextId(getNextID(node));
             cache.put(node.getNodeId(), entries);
-            
-            log.debug("Update cache for node with ID {0}: {1}", node, entries);
         } // else: not access controlled -> ignore.
         return entries;
     }
 
     /**
+     * Update cache for the given node id
+     * @param node The target node
+     * @return The list of entries present on the specified node or an empty list.
+     * @throws RepositoryException
+     */
+    private Entries updateCache(NodeImpl node) throws RepositoryException {
+        if ("T".equals(strategy)) {
+            return throttledUpdateCache(node);
+        } else if ("S".equals(strategy)) {
+            return synchronizedUpdateCache(node);
+        } else if ("P".equals(strategy)) {
+            return parallelUpdateCache(node);
+        } else {
+            // panic
+            throw new RuntimeException("invalid value for updateCacheStrategy: " + strategy);
+        }
+    }
+
+    /**
+     * See {@link CachingEntryCollector#updateCache(NodeImpl)} ; this variant runs fully synchronized
+     */
+    synchronized private Entries synchronizedUpdateCache(NodeImpl node) throws RepositoryException {
+        return internalUpdateCache(node);
+    }
+
+    /**
+     * See {@link CachingEntryCollector#updateCache(NodeImpl)} ; this variant runs fully parallel
+     */
+    private Entries parallelUpdateCache(NodeImpl node) throws RepositoryException {
+        return internalUpdateCache(node);
+    }
+
+    /**
+     * See {@link CachingEntryCollector#updateCache(NodeImpl)} ; this variant blocks the current
+     * thread if a concurrent update for the same node id takes place
+     */
+    private Entries throttledUpdateCache(NodeImpl node) throws RepositoryException {
+        NodeId id = node.getNodeId();
+        FutureEntries fe = null;
+        FutureEntries nfe = new FutureEntries();
+        boolean found = true;
+
+        fe = futures.putIfAbsent(id, nfe);
+        if (fe == null) {
+            found = false;
+            fe = nfe;
+        }
+
+        if (found) {
+            // we have found a previous FutureEntries object, so use it
+            return fe.get();
+        } else {
+            // otherwise obtain result and when done notify waiting FutureEntries
+            try {
+                Entries e = internalUpdateCache(node);
+                futures.remove(id);
+                fe.setResult(e);
+                return e;
+            } catch (Throwable problem) {
+                futures.remove(id);
+                fe.setProblem(problem);
+                if (problem instanceof RepositoryException) {
+                    throw (RepositoryException)problem;
+                } else {
+                    throw new RuntimeException(problem);
+                }
+            }
+        }
+    }
+
+    /**
+     * Find the next access control ancestor in the hierarchy 'null' indicates
+     * that there is no ac-controlled ancestor.
+     *
+     * @param node The target node for which the cache needs to be updated.
+     * @return The NodeId of the next access controlled ancestor in the hierarchy
+     * or null
+     */
+    private NodeId getNextID(NodeImpl node) throws RepositoryException {
+        NodeImpl n = node;
+        NodeId nextId = null;
+        while (nextId == null && !isRootId(n.getNodeId())) {
+            NodeId parentId = n.getParentId();
+            if (cache.containsKey(parentId)) {
+                nextId = parentId;
+            } else {
+                NodeImpl parent = (NodeImpl) n.getParent();
+                if (hasEntries(parent)) {
+                    nextId = parentId;
+                } else {
+                    // try next ancestor
+                    n = parent;
+                }
+            }
+        }
+        return nextId;
+    }
+
+    /**
+     * Returns {@code true} if the specified {@code nodeId} is the ID of the
+     * root node; false otherwise.
+     *
+     * @param nodeId The identifier of the node to be tested.
+     * @return {@code true} if the given id is the identifier of the root node.
+     */
+    private boolean isRootId(NodeId nodeId) {
+        return rootID.equals(nodeId);
+    }
+
+    /**
      * Evaluates if the given node is access controlled and holds a non-empty
      * rep:policy child node.
-     * 
+     *
      * @param n The node to test.
      * @return true if the specified node is access controlled and holds a
      * non-empty policy child node.
@@ -156,7 +257,7 @@ class CachingEntryCollector extends EntryCollector {
             return aclNode.hasNodes();
         }
 
-        // no acl defined here
+        // no ACL defined here
         return false;
     }
 
@@ -174,34 +275,182 @@ class CachingEntryCollector extends EntryCollector {
             }
             NodeId nodeId = (NodeId) key;
             int type = modifications.getType(nodeId);
-            synchronized (monitor) {
-                if ((type & POLICY_ADDED) == POLICY_ADDED) {
-                    // clear the complete cache since the nextAcNodeId may
-                    // have changed due to the added acl.
-                    cache.clear();
-                    break; // no need for further processing.
-                } else if ((type & POLICY_REMOVED) == POLICY_REMOVED) {
-                    // clear the entry and change the entries having a nextID
-                    // pointing to this node.
-                    Entries ce = cache.remove(nodeId);
-                    if (ce != null) {
-                        NodeId nextId = ce.getNextId();
-                        for (Entries entry : cache.values()) {
-                            if (nodeId.equals(entry.getNextId())) {
-                                entry.setNextId(nextId);
+            if ((type & POLICY_ADDED) == POLICY_ADDED) {
+                // clear the complete cache since the nextAcNodeId may
+                // have changed due to the added ACL.
+                log.debug("Policy added, clearing the cache");
+                cache.clear();
+                break; // no need for further processing.
+            } else if ((type & POLICY_REMOVED) == POLICY_REMOVED) {
+                // clear the entry and change the entries having a nextID
+                // pointing to this node.
+                cache.remove(nodeId, true);
+            } else if ((type & POLICY_MODIFIED) == POLICY_MODIFIED) {
+                // simply clear the cache entry -> reload upon next access.
+                cache.remove(nodeId, false);
+            } else if ((type & MOVE) == MOVE) {
+                // some sort of move operation that may affect the cache
+                log.debug("Move operation, clearing the cache");
+                cache.clear();
+                break; // no need for further processing.
+            }
+        }
+        super.notifyListeners(modifications);
+    }
+
+    /**
+     * A place holder for a yet to be computed {@link Entries} result 
+     */
+    private class FutureEntries {
+
+        private boolean ready = false;
+        private Entries result = null;
+        private Throwable problem = null;
+
+        synchronized public Entries get() throws RepositoryException {
+            while (!ready) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                }
+            }
+            if (problem != null) {
+                if (problem instanceof RepositoryException) {
+                    throw new RepositoryException(problem);
+                } else {
+                    throw new RuntimeException(problem);
+                }
+            }
+            return result;
+        }
+
+        synchronized public void setResult(Entries e) {
+            result = e;
+            ready = true;
+            notifyAll();
+        }
+
+        synchronized public void setProblem(Throwable t) {
+            problem = t;
+            ready = true;
+            notifyAll();
+        }
+    }
+
+    /**
+     * A cache to lookup the ACEs defined on a given (access controlled)
+     * node. The internal map uses the ID of the node as key while the value
+     * consists of {@Entries} objects that not only provide the ACEs defined
+     * for that node but also the ID of the next access controlled parent node.
+     */
+    private class EntryCache {
+
+        private final Map<NodeId, Entries> cache;
+        private Entries rootEntries;
+        private boolean specialCaseRoot = true;
+
+        @SuppressWarnings("unchecked")
+        public EntryCache() {
+            int maxsize = 5000;
+            String propname = "org.apache.jackrabbit.core.security.authorization.acl.CachingEntryCollector.maxsize";
+            try {
+                maxsize = Integer.parseInt(System.getProperty(propname, Integer.toString(maxsize)));
+            } catch (NumberFormatException ex) {
+                log.debug("Parsing system property " + propname + " with value: " + System.getProperty(propname), ex);
+            }
+
+            log.info("Creating cache with max size of: " + maxsize);
+
+            cache = new GrowingLRUMap(1024, maxsize);
+
+            String propsrname = "org.apache.jackrabbit.core.security.authorization.acl.CachingEntryCollector.scroot";
+            specialCaseRoot = Boolean.parseBoolean(System.getProperty(propsrname, "true"));
+
+            log.info("Root is special-cased: " + specialCaseRoot);
+        }
+
+        public boolean specialCasesRoot() {
+            return specialCaseRoot;
+        }
+
+        public boolean containsKey(NodeId id) {
+            if (specialCaseRoot && isRootId(id)) {
+                return rootEntries != null;
+            } else {
+                synchronized (cache) {
+                    return cache.containsKey(id);
+                }
+            }
+        }
+
+        public void clear() {
+            rootEntries = null;
+            synchronized (cache) {
+                cache.clear();
+            }
+        }
+
+        public Entries get(NodeId id) {
+            Entries result;
+
+            if (specialCaseRoot && isRootId(id)) {
+                result = rootEntries;
+            } else {
+                synchronized (cache) {
+                    result = cache.get(id);
+                }
+            }
+
+            if (result != null) {
+                log.debug("Cache hit for nodeId {}", id);
+            } else {
+                log.debug("Cache miss for nodeId {}", id);
+            }
+
+            return result;
+        }
+
+        public void put(NodeId id, Entries entries) {
+            log.debug("Updating cache for nodeId {}", id);
+
+            // fail early on potential cache corruption
+            if (id.equals(entries.getNextId())) {
+                throw new IllegalArgumentException("Trying to update cache entry for " + id + " with a circular reference");
+            }
+
+            if (specialCaseRoot && isRootId(id)) {
+                rootEntries = entries;
+            } else {
+                synchronized (cache) {
+                    cache.put(id, entries);
+                }
+            }
+        }
+
+        public void remove(NodeId id, boolean adjustNextIds) {
+            log.debug("Removing nodeId {} from cache", id);
+            Entries result;
+            synchronized (cache) {
+                if (specialCaseRoot && isRootId(id)) {
+                    result = rootEntries;
+                    rootEntries = null;
+                } else {
+                    result = cache.remove(id);
+                }
+
+                if (adjustNextIds && result != null) {
+                    NodeId nextId = result.getNextId();
+                    for (Entries entry : cache.values()) {
+                        if (id.equals(entry.getNextId())) {
+                            // fail early on potential cache corruption
+                            if (id.equals(nextId)) {
+                                throw new IllegalArgumentException("Trying to update cache entry for " + id + " with a circular reference");
                             }
+                            entry.setNextId(nextId);
                         }
                     }
-                } else if ((type & POLICY_MODIFIED) == POLICY_MODIFIED) {
-                    // simply clear the cache entry -> reload upon next access.
-                    cache.remove(nodeId);
-                } else if ((type & MOVE) == MOVE) {
-                    // some sort of move operation that may affect the cache
-                    cache.clear();
-                    break; // no need for further processing.
                 }
             }
         }
-        super.notifyListeners(modifications);
     }
 }
\ No newline at end of file
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CompiledPermissionsImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CompiledPermissionsImpl.java
index cf9e58e..b188fca 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CompiledPermissionsImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CompiledPermissionsImpl.java
@@ -39,7 +39,6 @@ import org.apache.jackrabbit.util.Text;
 
 import javax.jcr.ItemNotFoundException;
 import javax.jcr.RepositoryException;
-import javax.jcr.security.AccessControlEntry;
 import java.security.Principal;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -92,7 +91,7 @@ class CompiledPermissionsImpl extends AbstractCompiledPermissions implements Acc
         // retrieve all ACEs at path or at the direct ancestor of path that
         // apply for the principal names.
         NodeImpl n = ACLProvider.getNode(node, isAcItem);
-        Iterator<AccessControlEntry> entries = entryCollector.collectEntries(n, filter).iterator();
+        Iterator<Entry> entries = entryCollector.collectEntries(n, filter).iterator();
 
         /*
         Calculate privileges and permissions:
@@ -112,7 +111,7 @@ class CompiledPermissionsImpl extends AbstractCompiledPermissions implements Acc
         NodeId nodeId = (node == null) ? null : node.getNodeId();
 
         while (entries.hasNext()) {
-            ACLTemplate.Entry ace = (ACLTemplate.Entry) entries.next();
+            Entry ace = entries.next();
             /*
             Determine if the ACE also takes effect on the parent:
             Some permissions (e.g. add-node or removal) must be determined
@@ -261,8 +260,7 @@ class CompiledPermissionsImpl extends AbstractCompiledPermissions implements Acc
                      (see special treatment of remove, create or ac-specific
                       permissions).
                      */
-                    for (AccessControlEntry accessControlEntry : entryCollector.collectEntries(node, filter)) {
-                        ACLTemplate.Entry ace = (ACLTemplate.Entry) accessControlEntry;
+                    for (Entry ace : entryCollector.collectEntries(node, filter)) {
                         if (ace.getPrivilegeBits().includesRead()) {
                             canRead = ace.isAllow();
                             break;
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/Entry.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/Entry.java
new file mode 100644
index 0000000..5646ce8
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/Entry.java
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.security.authorization.acl;
+
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.ArrayList;
+import java.util.List;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.api.JackrabbitWorkspace;
+import org.apache.jackrabbit.api.security.principal.PrincipalManager;
+import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.security.authorization.AccessControlConstants;
+import org.apache.jackrabbit.core.security.authorization.GlobPattern;
+import org.apache.jackrabbit.core.security.authorization.PrivilegeBits;
+import org.apache.jackrabbit.core.security.authorization.PrivilegeManagerImpl;
+import org.apache.jackrabbit.core.value.InternalValue;
+import org.apache.jackrabbit.spi.Name;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Entry... TODO
+ */
+class Entry implements AccessControlConstants {
+
+    private static final Logger log = LoggerFactory.getLogger(ACLTemplate.class);
+
+    private final String principalName;
+    private final boolean isGroupEntry;
+    private final PrivilegeBits privilegeBits;
+    private final boolean isAllow;
+    private final NodeId id;
+    private final GlobPattern pattern;
+    private final boolean hasRestrictions;
+
+    private int hashCode;
+
+    private Entry(NodeId id, String principalName, boolean isGroupEntry,
+                  PrivilegeBits privilegeBits, boolean allow, String path, Value globValue) throws RepositoryException {
+
+        this.principalName = principalName;
+        this.isGroupEntry = isGroupEntry;
+        this.privilegeBits = privilegeBits;
+        this.isAllow = allow;
+        this.id = id;
+        this.pattern = calculatePattern(path, globValue);
+        this.hasRestrictions = (globValue != null);
+    }
+
+    static List<Entry> readEntries(NodeImpl aclNode, String path) throws RepositoryException {
+        if (aclNode == null || !NT_REP_ACL.equals(aclNode.getPrimaryNodeTypeName())) {
+            throw new IllegalArgumentException("Node must be of type 'rep:ACL'");
+        }
+        SessionImpl sImpl = (SessionImpl) aclNode.getSession();
+        PrincipalManager principalMgr = sImpl.getPrincipalManager();
+        PrivilegeManagerImpl privilegeMgr = (PrivilegeManagerImpl) ((JackrabbitWorkspace) sImpl.getWorkspace()).getPrivilegeManager();
+
+        NodeId nodeId = aclNode.getParentId();
+
+        List<Entry> entries = new ArrayList<Entry>();
+        // load the entries:
+        NodeIterator itr = aclNode.getNodes();
+        while (itr.hasNext()) {
+            NodeImpl aceNode = (NodeImpl) itr.nextNode();
+            try {
+                String principalName = aceNode.getProperty(P_PRINCIPAL_NAME).getString();
+                boolean isGroupEntry = false;
+                Principal princ = principalMgr.getPrincipal(principalName);
+                if (princ != null) {
+                    isGroupEntry = (princ instanceof Group);
+                }
+
+                InternalValue[] privValues = aceNode.getProperty(P_PRIVILEGES).internalGetValues();
+                Name[] privNames = new Name[privValues.length];
+                for (int i = 0; i < privValues.length; i++) {
+                    privNames[i] = privValues[i].getName();
+                }
+
+                Value globValue = null;
+                if (aceNode.hasProperty(P_GLOB)) {
+                    globValue = aceNode.getProperty(P_GLOB).getValue();
+                }
+
+                boolean isAllow = NT_REP_GRANT_ACE.equals(aceNode.getPrimaryNodeTypeName());
+                Entry ace = new Entry(nodeId, principalName, isGroupEntry, privilegeMgr.getBits(privNames), isAllow, path, globValue);
+                entries.add(ace);
+            } catch (RepositoryException e) {
+                log.debug("Failed to build ACE from content. {}", e.getMessage());
+            }
+        }
+
+        return entries;
+    }
+
+    private static GlobPattern calculatePattern(String path, Value globValue) throws RepositoryException {
+        if (path == null) {
+            return null;
+        } else {
+            if (globValue == null) {
+                return GlobPattern.create(path);
+            } else {
+                return GlobPattern.create(path, globValue.getString());
+            }
+        }
+    }
+
+    /**
+     * @param nodeId
+     * @return <code>true</code> if this entry is defined on the node
+     * at <code>nodeId</code>
+     */
+    boolean isLocal(NodeId nodeId) {
+        return id != null && id.equals(nodeId);
+    }
+
+    /**
+     *
+     * @param jcrPath
+     * @return
+     */
+    boolean matches(String jcrPath) {
+        return pattern != null && pattern.matches(jcrPath);
+    }
+
+    PrivilegeBits getPrivilegeBits() {
+        return privilegeBits;
+    }
+
+    boolean isAllow() {
+        return isAllow;
+    }
+
+    String getPrincipalName() {
+        return principalName;
+    }
+
+    boolean isGroupEntry() {
+        return isGroupEntry;
+    }
+
+    boolean hasRestrictions() {
+        return hasRestrictions;
+    }
+
+    //-------------------------------------------------------------< Object >---
+    /**
+     * @see Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        if (hashCode == -1) {
+            int h = 17;
+            h = 37 * h + principalName.hashCode();
+            h = 37 * h + privilegeBits.hashCode();
+            h = 37 * h + Boolean.valueOf(isAllow).hashCode();
+            h = 37 * h + pattern.hashCode();
+            hashCode = h;
+        }
+        return hashCode;
+    }
+
+    /**
+     * @see Object#equals(Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj instanceof Entry) {
+            Entry other = (Entry) obj;
+            return principalName.equals(other.principalName) &&
+                   privilegeBits.equals(other.privilegeBits) &&
+                   isAllow == other.isAllow &&
+                   pattern.equals(other.pattern);
+        }
+        return false;
+    }
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryCollector.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryCollector.java
index bb87366..887ff03 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryCollector.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryCollector.java
@@ -36,7 +36,6 @@ import javax.jcr.observation.Event;
 import javax.jcr.observation.EventIterator;
 import javax.jcr.observation.EventListener;
 import javax.jcr.observation.ObservationManager;
-import javax.jcr.security.AccessControlEntry;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -130,16 +129,16 @@ public class EntryCollector extends AccessControlObserver implements AccessContr
      * @return
      * @throws RepositoryException
      */
-    protected List<AccessControlEntry> collectEntries(NodeImpl node, EntryFilter filter) throws RepositoryException {
-        LinkedList<AccessControlEntry> userAces = new LinkedList<AccessControlEntry>();
-        LinkedList<AccessControlEntry> groupAces = new LinkedList<AccessControlEntry>();
+    protected List<Entry> collectEntries(NodeImpl node, EntryFilter filter) throws RepositoryException {
+        LinkedList<Entry> userAces = new LinkedList<Entry>();
+        LinkedList<Entry> groupAces = new LinkedList<Entry>();
 
         if (node == null) {
             // repository level permissions
             NodeImpl root = (NodeImpl) systemSession.getRootNode();
             if (ACLProvider.isRepoAccessControlled(root)) {
                 NodeImpl aclNode = root.getNode(N_REPO_POLICY);
-                filterEntries(filter, new ACLTemplate(aclNode).getEntries(), userAces, groupAces);
+                filterEntries(filter, Entry.readEntries(aclNode, null), userAces, groupAces);
             }
         } else {
             filterEntries(filter, getEntries(node).getACEs(), userAces, groupAces);
@@ -151,7 +150,7 @@ public class EntryCollector extends AccessControlObserver implements AccessContr
             }
         }
 
-        List<AccessControlEntry> entries = new ArrayList<AccessControlEntry>(userAces.size() + groupAces.size());
+        List<Entry> entries = new ArrayList<Entry>(userAces.size() + groupAces.size());
         entries.addAll(userAces);
         entries.addAll(groupAces);
 
@@ -167,9 +166,9 @@ public class EntryCollector extends AccessControlObserver implements AccessContr
      * @param groupAces
      */
     @SuppressWarnings("unchecked")
-    private static void filterEntries(EntryFilter filter, List<AccessControlEntry> aces,
-                                      LinkedList<AccessControlEntry> userAces,
-                                      LinkedList<AccessControlEntry> groupAces) {
+    private static void filterEntries(EntryFilter filter, List<Entry> aces,
+                                      LinkedList<Entry> userAces,
+                                      LinkedList<Entry> groupAces) {
         if (!aces.isEmpty() && filter != null) {
             filter.filterEntries(aces, userAces, groupAces);
         }
@@ -185,11 +184,11 @@ public class EntryCollector extends AccessControlObserver implements AccessContr
      * @throws RepositoryException
      */
     protected Entries getEntries(NodeImpl node) throws RepositoryException {
-        List<AccessControlEntry> aces;
+        List<Entry> aces;
         if (ACLProvider.isAccessControlled(node)) {
             // collect the aces of that node.
             NodeImpl aclNode = node.getNode(N_POLICY);
-            aces = new ACLTemplate(aclNode).getEntries();
+            aces = Entry.readEntries(aclNode, node.getPath());
         } else {
             // not access controlled
             aces = Collections.emptyList();
@@ -438,15 +437,15 @@ public class EntryCollector extends AccessControlObserver implements AccessContr
      */
     static class Entries {
 
-        private final List<AccessControlEntry> aces;
+        private final List<Entry> aces;
         private NodeId nextId;
 
-        Entries(List<AccessControlEntry> aces, NodeId nextId) {
+        Entries(List<Entry> aces, NodeId nextId) {
             this.aces = aces;
             this.nextId = nextId;
         }
 
-        List<AccessControlEntry> getACEs() {
+        List<Entry> getACEs() {
             return aces;
         }
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilter.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilter.java
index ea33009..2db93f9 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilter.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilter.java
@@ -16,7 +16,6 @@
  */
 package org.apache.jackrabbit.core.security.authorization.acl;
 
-import javax.jcr.security.AccessControlEntry;
 import java.util.List;
 
 /**
@@ -24,6 +23,6 @@ import java.util.List;
  */
 public interface EntryFilter {
 
-    void filterEntries(List<AccessControlEntry> entries, List<AccessControlEntry>... resultLists);
+    void filterEntries(List<Entry> entries, List<Entry>... resultLists);
 
 }
\ No newline at end of file
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilterImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilterImpl.java
index d8ff19b..b298f7a 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilterImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilterImpl.java
@@ -24,8 +24,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.jcr.RepositoryException;
-import javax.jcr.security.AccessControlEntry;
-import java.security.acl.Group;
 import java.util.Collection;
 import java.util.List;
 
@@ -71,16 +69,16 @@ class EntryFilterImpl implements EntryFilter {
      * @param resultLists
      * @see EntryFilter#filterEntries(java.util.List, java.util.List[])
      */
-    public void filterEntries(List<AccessControlEntry> entries, List<AccessControlEntry>... resultLists) {
+    public void filterEntries(List<Entry> entries, List<Entry>... resultLists) {
         if (resultLists.length == 2) {
-            List<AccessControlEntry> userAces = resultLists[0];
-            List<AccessControlEntry> groupAces = resultLists[1];
+            List<Entry> userAces = resultLists[0];
+            List<Entry> groupAces = resultLists[1];
 
             int uInsertIndex = userAces.size();
             int gInsertIndex = groupAces.size();
 
             // first collect aces present on the given aclNode.
-            for (AccessControlEntry ace : entries) {
+            for (Entry ace : entries) {
                 // only process ace if 'principalName' is contained in the given set
                 if (matches(ace)) {
                     // add it to the proper list (e.g. separated by principals)
@@ -88,7 +86,7 @@ class EntryFilterImpl implements EntryFilter {
                      * NOTE: access control entries must be collected in reverse
                      * order in order to assert proper evaluation.
                      */
-                    if (ace.getPrincipal() instanceof Group) {
+                    if (ace.isGroupEntry()) {
                         groupAces.add(gInsertIndex, ace);
                     } else {
                         userAces.add(uInsertIndex, ace);
@@ -100,9 +98,8 @@ class EntryFilterImpl implements EntryFilter {
         }
     }
 
-    private boolean matches(AccessControlEntry ace) {
-        if (principalNames == null || principalNames.contains(ace.getPrincipal().getName())) {
-            ACLTemplate.Entry entry = (ACLTemplate.Entry) ace;
+    private boolean matches(Entry entry) {
+        if (principalNames == null || principalNames.contains(entry.getPrincipalName())) {
             if (!entry.hasRestrictions()) {
                 // short cut: there is no glob-restriction -> the entry matches
                 // because it is either defined on the node or inherited.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLProvider.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLProvider.java
index 30a81ce..cf3a9d3 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLProvider.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLProvider.java
@@ -205,7 +205,7 @@ public class ACLProvider extends AbstractAccessControlProvider implements Access
             Query q = qm.createQuery(stmt.toString(), Query.XPATH);
             result = q.execute();
         } catch (RepositoryException e) {
-            log.error("Unexpected error while searching effective policies.", e.getMessage());
+            log.error("Unexpected error while searching effective policies. {}", e.getMessage());
             throw new UnsupportedOperationException("Retrieve effective policies at absPath '" +jcrPath+ "' not supported.", e);
         }
 
@@ -267,7 +267,7 @@ public class ACLProvider extends AbstractAccessControlProvider implements Access
                 return new ACLEditor((SessionImpl) editingSession, session.getQPath(acRoot.getPath()));
             } catch (RepositoryException e) {
                 // should never get here
-                log.error("Internal error: ", e.getMessage());
+                log.error("Internal error: {}", e.getMessage());
             }
         }
 
@@ -522,7 +522,7 @@ public class ACLProvider extends AbstractAccessControlProvider implements Access
                 }
             } catch (RepositoryException e) {
                 // should never get here
-                log.warn("Internal error: ", e.getMessage());
+                log.warn("Internal error: {}", e.getMessage());
             }
         }
     }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLTemplate.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLTemplate.java
index 02ca27c..f5cb518 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLTemplate.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLTemplate.java
@@ -107,7 +107,6 @@ class ACLTemplate extends AbstractACLTemplate {
         if (acNode != null && acNode.hasNode(N_POLICY)) {
             // build the list of policy entries;
             NodeImpl aclNode = acNode.getNode(N_POLICY);
-            AccessControlManager acMgr = aclNode.getSession().getAccessControlManager();
 
             // loop over all entries in the aclNode for the princ-Principal
             for (NodeIterator aceNodes = aclNode.getNodes(); aceNodes.hasNext();) {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/EntriesCache.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/EntriesCache.java
index f035b0d..bac78a1 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/EntriesCache.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/EntriesCache.java
@@ -210,7 +210,7 @@ class EntriesCache extends AccessControlObserver implements AccessControlConstan
 
             } catch (RepositoryException e) {
                 // should never get here
-                log.warn("Internal error: ", e.getMessage());
+                log.warn("Internal error: {}", e.getMessage());
             }
 
         }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/AbstractPrincipalProvider.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/AbstractPrincipalProvider.java
index 21fdee1..43250a0 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/AbstractPrincipalProvider.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/AbstractPrincipalProvider.java
@@ -26,7 +26,7 @@ import java.util.Properties;
  * caching facility. Extending classes should only deal with the retrieval of
  * {@link Principal}s from their source, the caching of the principals is done
  * by this implementation.
- * <p/>
+ * <p>
  * The {@link PrincipalProvider} methods that that involve searching like
  * {@link PrincipalProvider#getGroupMembership(Principal)} are not cached.
  */
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/DefaultPrincipalProvider.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/DefaultPrincipalProvider.java
index 016883f..1853bde 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/DefaultPrincipalProvider.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/DefaultPrincipalProvider.java
@@ -46,16 +46,16 @@ import java.util.Set;
  * Each {@link Authorizable} accessible via {@link UserManager}
  * is respected and the provider serves {@link Authorizable#getPrincipal()
  * Principal}s retrieved from those <code>Authorizable</code> objects.
- * <p/>
+ * <p>
  * In addition this provider exposes the <i>everyone</i> principal, which has no
  * content (user/group) representation.
- * <p/>
+ * <p>
  * Unless explicitly configured (see {@link #NEGATIVE_ENTRY_KEY negative entry
  * option} this implementation of the <code>PrincipalProvider</code> interface
  * caches both positive and negative (null) results of the {@link #providePrincipal}
  * method. The cache is kept up to date by observation listening to creation
  * and removal of users and groups.
- * <p/>
+ * <p>
  * Membership cache:<br>
  * In addition to the caching provided by <code>AbstractPrincipalProvider</code>
  * this implementation keeps an extra membership cache, which is notified in
@@ -113,7 +113,7 @@ public class DefaultPrincipalProvider extends AbstractPrincipalProvider implemen
     //------------------------------------------< AbstractPrincipalProvider >---
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * This implementation uses the user and node resolver to find the
      * appropriate nodes.
      */
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalManagerImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalManagerImpl.java
index 866969a..f9d2624 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalManagerImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalManagerImpl.java
@@ -28,6 +28,7 @@ import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 
 import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
+import org.apache.jackrabbit.api.security.principal.JackrabbitPrincipal;
 import org.apache.jackrabbit.api.security.principal.PrincipalIterator;
 import org.apache.jackrabbit.api.security.principal.PrincipalManager;
 
@@ -211,7 +212,7 @@ public class PrincipalManagerImpl implements PrincipalManager {
      * due to the fact, that the principal provider is not bound to a particular
      * Session object.
      */
-    private class CheckedGroup implements Group {
+    private class CheckedGroup implements Group, JackrabbitPrincipal {
 
         final Group delegatee;
         private final PrincipalProvider provider;
@@ -258,7 +259,7 @@ public class PrincipalManagerImpl implements PrincipalManager {
 
         @Override
         public boolean equals(Object obj) {
-            return delegatee.equals(obj);
+            return delegatee.equals(obj instanceof CheckedGroup ? ((CheckedGroup) obj).delegatee : obj);
         }
     }
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalProvider.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalProvider.java
index 1ec34ae..5f95096 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalProvider.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalProvider.java
@@ -85,7 +85,7 @@ public interface PrincipalProvider {
      * evaluates to <code>true</code>. A principal is an indirect member of a
      * group if any of its groups (to any degree of separation) is direct member
      * of the group.
-     * <p/>
+     * <p>
      * Example:<br>
      * If Principal is member of Group A, and Group A is member of
      * Group B, this method will return Group A and Group B.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalProviderRegistry.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalProviderRegistry.java
index 098f4f6..1ebaf8a 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalProviderRegistry.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/principal/PrincipalProviderRegistry.java
@@ -31,7 +31,7 @@ public interface PrincipalProviderRegistry {
      * registry expects the properties to contain a
      * {@link org.apache.jackrabbit.core.config.LoginModuleConfig#PARAM_PRINCIPAL_PROVIDER_CLASS}
      * to be able to create a instance of PrincipalProvider.
-     * <p/>
+     * <p>
      * The Properties will be passed to the instantiated Provider via
      * {@link PrincipalProvider#init(Properties)}
      *
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/AuthorizableImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/AuthorizableImpl.java
index 75035c7..9bbc087 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/AuthorizableImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/AuthorizableImpl.java
@@ -20,6 +20,7 @@ import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.Group;
 import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.commons.iterator.RangeIteratorAdapter;
 import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.id.NodeId;
@@ -346,7 +347,10 @@ abstract class AuthorizableImpl implements Authorizable, UserConstants {
         MembershipCache cache = userManager.getMembershipCache();
         String nid = node.getIdentifier();
 
+        final long t0 = System.nanoTime();
+        boolean collect = false;
         if (node.getSession().hasPendingChanges()) {
+            collect = true;
             // avoid retrieving outdated cache entries or filling the cache with
             // invalid data due to group-membership changes pending on the
             // current session.
@@ -359,7 +363,7 @@ abstract class AuthorizableImpl implements Authorizable, UserConstants {
             //  retrieve cached membership. there are no pending changes.
             groupNodeIds = (includeIndirect) ? cache.getMemberOf(nid) : cache.getDeclaredMemberOf(nid);
         }
-
+        final long t1 = System.nanoTime();
         Set<Group> groups = new HashSet<Group>(groupNodeIds.size());
         for (String identifier : groupNodeIds) {
             try {
@@ -370,7 +374,20 @@ abstract class AuthorizableImpl implements Authorizable, UserConstants {
                 // group node doesn't exist or cannot be read -> ignore.
             }
         }
-        return groups.iterator();
+        final long t2 = System.nanoTime();
+        if (log.isDebugEnabled()) {
+            log.debug("Collected {} {} group ids for [{}] in {}us, loaded {} groups in {}us (collect={}, cachesize={})", new Object[]{
+                    groupNodeIds.size(),
+                    includeIndirect ? "all" : "declared",
+                    getID(),
+                    (t1-t0) / 1000,
+                    groups.size(),
+                    (t2-t1) / 1000,
+                    collect,
+                    cache.getSize()
+            });
+        }
+        return new RangeIteratorAdapter(groups.iterator(), groups.size());
     }
 
     /**
@@ -429,7 +446,9 @@ abstract class AuthorizableImpl implements Authorizable, UserConstants {
         Name pName = getSession().getQName(propertyName);
         return P_PRINCIPAL_NAME.equals(pName)
                 || P_MEMBERS.equals(pName)
-                || P_IMPERSONATORS.equals(pName) || P_PASSWORD.equals(pName);
+                || P_IMPERSONATORS.equals(pName)
+                || P_DISABLED.equals(pName)
+                || P_PASSWORD.equals(pName);
     }
 
     /**
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/GroupImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/GroupImpl.java
index dcbec26..8cb211f 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/GroupImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/GroupImpl.java
@@ -16,6 +16,27 @@
  */
 package org.apache.jackrabbit.core.security.user;
 
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.Group;
 import org.apache.jackrabbit.api.security.user.UserManager;
@@ -25,6 +46,7 @@ import org.apache.jackrabbit.commons.flat.PropertySequence;
 import org.apache.jackrabbit.commons.flat.Rank;
 import org.apache.jackrabbit.commons.flat.TreeManager;
 import org.apache.jackrabbit.commons.iterator.LazyIteratorChain;
+import org.apache.jackrabbit.commons.iterator.RangeIteratorAdapter;
 import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.PropertyImpl;
 import org.apache.jackrabbit.core.session.SessionContext;
@@ -34,28 +56,6 @@ import org.apache.jackrabbit.util.Text;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-
-import java.io.IOException;
-import java.io.ObjectOutputStream;
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Set;
-
 /**
  * GroupImpl...
  */
@@ -189,10 +189,19 @@ class GroupImpl extends AuthorizableImpl implements Group {
     }
 
     //--------------------------------------------------------------------------
-
+    /**
+     * Retrieve the membership provider for this group. This method deals with
+     * members stored in the <code>P_MEMBERS</code> property and with those
+     * repositories the store group members in a separate tree underneath the
+     * <code>N_MEMBERS</code> node.
+     *
+     * @param node The node associated with this group.
+     * @return an instance of <code>MembershipProvider</code>.
+     * @throws RepositoryException If an error occurs.
+     */
     private MembershipProvider getMembershipProvider(NodeImpl node) throws RepositoryException {
         MembershipProvider msp;
-        if (userManager.getGroupMembershipSplitSize() > 0) {
+        if (userManager.hasMemberSplitSize()) {
             if (node.hasNode(N_MEMBERS) || !node.hasProperty(P_MEMBERS)) {
                 msp = new NodeBasedMembershipProvider(node);
             } else {
@@ -204,7 +213,7 @@ class GroupImpl extends AuthorizableImpl implements Group {
 
         if (node.hasProperty(P_MEMBERS) && node.hasNode(N_MEMBERS)) {
             log.warn("Found members node and members property on node {}. Ignoring {} members", node,
-                    userManager.getGroupMembershipSplitSize() > 0 ? "property" : "node");
+                    userManager.hasMemberSplitSize() ? "property" : "node");
         }
 
         return msp;
@@ -212,9 +221,9 @@ class GroupImpl extends AuthorizableImpl implements Group {
 
     /**
      * @param includeIndirect If <code>true</code> all members of this group
-     *                        will be return; otherwise only the declared members.
-     * @param type            Any of {@link UserManager#SEARCH_TYPE_AUTHORIZABLE},
-     *                        {@link UserManager#SEARCH_TYPE_GROUP}, {@link UserManager#SEARCH_TYPE_USER}.
+     * will be return; otherwise only the declared members.
+     * @param type Any of {@link UserManager#SEARCH_TYPE_AUTHORIZABLE},
+     * {@link UserManager#SEARCH_TYPE_GROUP}, {@link UserManager#SEARCH_TYPE_USER}.
      * @return A collection of members of this group.
      * @throws RepositoryException If an error occurs while collecting the members.
      */
@@ -228,7 +237,7 @@ class GroupImpl extends AuthorizableImpl implements Group {
      *
      * @param newMember The new member to be tested for cyclic membership.
      * @return true if the 'newMember' is a group and 'this' is an declared or
-     *         inherited member of it.
+     * inherited member of it.
      * @throws javax.jcr.RepositoryException If an error occurs.
      */
     private boolean isCyclicMembership(AuthorizableImpl newMember) throws RepositoryException {
@@ -257,7 +266,7 @@ class GroupImpl extends AuthorizableImpl implements Group {
 
     static PropertySequence getPropertySequence(Node nMembers, UserManagerImpl userManager) throws RepositoryException {
         Comparator<String> order = Rank.comparableComparator();
-        int maxChildren = userManager.getGroupMembershipSplitSize();
+        int maxChildren = userManager.getMemberSplitSize();
         int minChildren = maxChildren / 2;
 
         TreeManager treeManager = new BTreeManager(nMembers, minChildren, maxChildren, order,
@@ -272,8 +281,6 @@ class GroupImpl extends AuthorizableImpl implements Group {
      */
     private class NodeBasedGroup extends NodeBasedPrincipal implements java.security.acl.Group {
 
-        private Set<Principal> members;
-
         private NodeBasedGroup(String name) {
             super(name);
         }
@@ -349,16 +356,14 @@ class GroupImpl extends AuthorizableImpl implements Group {
          * @return the members of this group principal.
          */
         private Collection<Principal> getMembers() {
-            if (members == null) {
-                members = new HashSet<Principal>();
-                try {
-                    for (Iterator<Authorizable> it = GroupImpl.this.getMembers(); it.hasNext(); ) {
-                        members.add(it.next().getPrincipal());
-                    }
-                } catch (RepositoryException e) {
-                    // should not occur.
-                    log.error("Unable to retrieve Group members.");
+            Set<Principal> members = new HashSet<Principal>();
+            try {
+                for (Iterator<Authorizable> it = GroupImpl.this.getMembers(); it.hasNext(); ) {
+                    members.add(it.next().getPrincipal());
                 }
+            } catch (RepositoryException e) {
+                // should not occur.
+                log.error("Unable to retrieve Group members.");
             }
             return members;
         }
@@ -461,7 +466,7 @@ class GroupImpl extends AuthorizableImpl implements Group {
                 if (includeIndirect) {
                     return includeIndirect(toAuthorizables(members, type), type);
                 } else {
-                    return toAuthorizables(members, type);
+                    return new RangeIteratorAdapter(toAuthorizables(members, type), members.length);
                 }
             } else {
                 return Iterators.empty();
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/MembershipCache.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/MembershipCache.java
index bb687b7..45eebc9 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/MembershipCache.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/MembershipCache.java
@@ -16,36 +16,35 @@
  */
 package org.apache.jackrabbit.core.security.user;
 
-import org.apache.jackrabbit.core.cache.GrowingLRUMap;
-import org.apache.jackrabbit.core.NodeImpl;
-import org.apache.jackrabbit.core.PropertyImpl;
-import org.apache.jackrabbit.core.SessionImpl;
-import org.apache.jackrabbit.core.SessionListener;
-import org.apache.jackrabbit.core.nodetype.NodeTypeImpl;
-import org.apache.jackrabbit.core.observation.SynchronousEventListener;
-import org.apache.jackrabbit.spi.Name;
-import org.apache.jackrabbit.spi.commons.name.NameConstants;
-import org.apache.jackrabbit.util.Text;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 import javax.jcr.AccessDeniedException;
 import javax.jcr.ItemNotFoundException;
-import javax.jcr.ItemVisitor;
 import javax.jcr.Node;
+import javax.jcr.NodeIterator;
 import javax.jcr.Property;
 import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.Value;
 import javax.jcr.observation.Event;
 import javax.jcr.observation.EventIterator;
-import javax.jcr.util.TraversingItemVisitor;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+
+import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.PropertyImpl;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.SessionListener;
+import org.apache.jackrabbit.core.cache.ConcurrentCache;
+import org.apache.jackrabbit.core.nodetype.NodeTypeImpl;
+import org.apache.jackrabbit.core.observation.SynchronousEventListener;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * <code>MembershipCache</code>...
@@ -57,20 +56,26 @@ public class MembershipCache implements UserConstants, SynchronousEventListener,
      */
     private static final Logger log = LoggerFactory.getLogger(MembershipCache.class);
 
+    /**
+     * The maximum size of this cache
+     */
+    private static final int MAX_CACHE_SIZE =
+            Integer.getInteger("org.apache.jackrabbit.MembershipCache", 5000);
+
     private final SessionImpl systemSession;
     private final String groupsPath;
     private final boolean useMembersNode;
     private final String pMembers;
-    private final Map<String, Collection<String>> cache;
+    private final ConcurrentCache<String, Collection<String>> cache;
 
-    @SuppressWarnings("unchecked")
     MembershipCache(SessionImpl systemSession, String groupsPath, boolean useMembersNode) throws RepositoryException {
         this.systemSession = systemSession;
         this.groupsPath = (groupsPath == null) ? UserConstants.GROUPS_PATH : groupsPath;
         this.useMembersNode = useMembersNode;
 
         pMembers = systemSession.getJCRName(UserManagerImpl.P_MEMBERS);
-        cache = new GrowingLRUMap(1024, 5000);
+        cache = new ConcurrentCache<String, Collection<String>>("MembershipCache", 16);
+        cache.setMaxMemorySize(MAX_CACHE_SIZE);
 
         String[] ntNames = new String[] {
                 systemSession.getJCRName(UserConstants.NT_REP_GROUP),
@@ -87,6 +92,7 @@ public class MembershipCache implements UserConstants, SynchronousEventListener,
         // make sure the membership cache is informed if the system session is
         // logged out in order to stop listening to events.
         systemSession.addListener(this);
+        log.debug("Membership cache initialized. Max Size = {}", MAX_CACHE_SIZE);
     }
 
 
@@ -132,9 +138,8 @@ public class MembershipCache implements UserConstants, SynchronousEventListener,
         }
 
         if (clear) {
-            synchronized (cache) {
-                cache.clear();
-            }
+            cache.clear();
+            log.debug("Membership cache cleared because of observation event.");
         }
     }
 
@@ -166,7 +171,7 @@ public class MembershipCache implements UserConstants, SynchronousEventListener,
      * authorizable in question is declared member of.
      * @throws RepositoryException If an error occurs.
      */
-    synchronized Collection<String> getDeclaredMemberOf(String authorizableNodeIdentifier) throws RepositoryException {
+    Collection<String> getDeclaredMemberOf(String authorizableNodeIdentifier) throws RepositoryException {
         return declaredMemberOf(authorizableNodeIdentifier);
     }
 
@@ -177,13 +182,28 @@ public class MembershipCache implements UserConstants, SynchronousEventListener,
      * authorizable in question is a direct or indirect member of.
      * @throws RepositoryException If an error occurs.
      */
-    synchronized Collection<String> getMemberOf(String authorizableNodeIdentifier) throws RepositoryException {
+    Collection<String> getMemberOf(String authorizableNodeIdentifier) throws RepositoryException {
         Set<String> groupNodeIds = new HashSet<String>();
         memberOf(authorizableNodeIdentifier, groupNodeIds);
         return Collections.unmodifiableCollection(groupNodeIds);
     }
 
     /**
+     * Returns the size of the membership cache
+     * @return the size
+     */
+    int getSize() {
+        return (int) cache.getElementCount();
+    }
+
+    /**
+     * For testing purposes only.
+     */
+    void clear() {
+        cache.clear();
+    }
+
+    /**
      * Collects the declared memberships for the specified identifier of an
      * authorizable using the specified session.
      * 
@@ -195,9 +215,30 @@ public class MembershipCache implements UserConstants, SynchronousEventListener,
      * @throws RepositoryException If an error occurs.
      */
     Collection<String> collectDeclaredMembership(String authorizableNodeIdentifier, Session session) throws RepositoryException {
+        final long t0 = System.nanoTime();
+
         Collection<String> groupNodeIds = collectDeclaredMembershipFromReferences(authorizableNodeIdentifier, session);
+
+        final long t1 = System.nanoTime();
+        if (log.isDebugEnabled()) {
+            log.debug("  collected {} groups for {} via references in {}us", new Object[]{
+                    groupNodeIds == null ? -1 : groupNodeIds.size(),
+                    authorizableNodeIdentifier,
+                    (t1-t0) / 1000
+            });
+        }
+
         if (groupNodeIds == null) {
             groupNodeIds = collectDeclaredMembershipFromTraversal(authorizableNodeIdentifier, session);
+
+            final long t2 = System.nanoTime();
+            if (log.isDebugEnabled()) {
+                log.debug("  collected {} groups for {} via traversal in {}us", new Object[]{
+                        groupNodeIds == null ? -1 : groupNodeIds.size(),
+                        authorizableNodeIdentifier,
+                        (t2-t1) / 1000
+                });
+            }
         }
         return groupNodeIds;
     }
@@ -221,19 +262,27 @@ public class MembershipCache implements UserConstants, SynchronousEventListener,
 
     //------------------------------------------------------------< private >---
     /**
-     * @param authorizableNodeIdentifier
-     * @return
-     * @throws RepositoryException
+     * Collects the groups where the given authorizable is a declared member of. If the information is not cached, it
+     * is collected from the repository.
+     *
+     * @param authorizableNodeIdentifier Identifier of the authorizable node
+     * @return the collection of groups where the authorizable is a declared member of
+     * @throws RepositoryException if an error occurs
      */
     private Collection<String> declaredMemberOf(String authorizableNodeIdentifier) throws RepositoryException {
+        final long t0 = System.nanoTime();
+
         Collection<String> groupNodeIds = cache.get(authorizableNodeIdentifier);
+
+        boolean wasCached = true;
         if (groupNodeIds == null) {
+            wasCached = false;
             // retrieve a new session with system-subject in order to avoid
             // concurrent read operations using the system session of this workspace.
             Session session = getSession();
             try {
                 groupNodeIds = collectDeclaredMembership(authorizableNodeIdentifier, session);
-                cache.put(authorizableNodeIdentifier, Collections.unmodifiableCollection(groupNodeIds));
+                cache.put(authorizableNodeIdentifier, Collections.unmodifiableCollection(groupNodeIds), 1);
             }
             finally {
                 // release session if it isn't the original system session
@@ -242,14 +291,27 @@ public class MembershipCache implements UserConstants, SynchronousEventListener,
                 }
             }
         }
+
+        if (log.isDebugEnabled()) {
+            final long t1 = System.nanoTime();
+            log.debug("Membership cache {} {} declared memberships of {} in {}us. cache size = {}", new Object[]{
+                    wasCached ? "returns" : "collected",
+                    groupNodeIds.size(),
+                    authorizableNodeIdentifier,
+                    (t1-t0) / 1000,
+                    cache.getElementCount()
+            });
+        }
         return groupNodeIds;
     }
 
     /**
-     * 
-     * @param authorizableNodeIdentifier
-     * @param groupNodeIds
-     * @throws RepositoryException
+     * Collects the groups where the given authorizable is a member of by recursively fetching the declared memberships
+     * via {@link #declaredMemberOf(String)} (cached).
+     *
+     * @param authorizableNodeIdentifier Identifier of the authorizable node
+     * @param groupNodeIds Map to receive the node ids of the groups
+     * @throws RepositoryException if an error occurs
      */
     private void memberOf(String authorizableNodeIdentifier, Collection<String> groupNodeIds) throws RepositoryException {
         Collection<String> declared = declaredMemberOf(authorizableNodeIdentifier);
@@ -261,11 +323,13 @@ public class MembershipCache implements UserConstants, SynchronousEventListener,
     }
 
     /**
-     * 
-     * @param authorizableNodeIdentifier
-     * @param groupNodeIds
-     * @param session
-     * @throws RepositoryException
+     * Collects the groups where the given authorizable is a member of by recursively fetching the declared memberships
+     * by reading the relations from the repository (uncached!).
+     *
+     * @param authorizableNodeIdentifier Identifier of the authorizable node
+     * @param groupNodeIds Map to receive the node ids of the groups
+     * @param session the session to read from
+     * @throws RepositoryException if an error occurs
      */
     private void memberOf(String authorizableNodeIdentifier, Collection<String> groupNodeIds, Session session) throws RepositoryException {
         Collection<String> declared = collectDeclaredMembership(authorizableNodeIdentifier, session);
@@ -277,11 +341,14 @@ public class MembershipCache implements UserConstants, SynchronousEventListener,
     }
 
     /**
-     * 
-     * @param authorizableNodeIdentifier
-     * @param session
-     * @return
-     * @throws RepositoryException
+     * Collects the declared memberships for the given authorizable by resolving the week references to the authorizable.
+     * If the lookup fails, <code>null</code> is returned. This most likely the case if the authorizable does not exit (yet)
+     * in the  session that is used for the lookup.
+     *
+     * @param authorizableNodeIdentifier Identifier of the authorizable node
+     * @param session the session to read from
+     * @return a collection of group node ids or <code>null</code> if the lookup failed.
+     * @throws RepositoryException if an error occurs
      */
     private Collection<String> collectDeclaredMembershipFromReferences(String authorizableNodeIdentifier,
                                                                        Session session) throws RepositoryException {
@@ -316,7 +383,7 @@ public class MembershipCache implements UserConstants, SynchronousEventListener,
                 } else {
                     // weak-ref property 'rep:members' that doesn't reside under an
                     // group node -> doesn't represent a valid group member.
-                    log.debug("Invalid member reference to '" + this + "' -> Not included in membership set.");
+                    log.debug("Invalid member reference to '{}' -> Not included in membership set.", this);
                 }
             } catch (ItemNotFoundException e) {
                 // group node doesn't exist  -> -> ignore exception
@@ -331,6 +398,14 @@ public class MembershipCache implements UserConstants, SynchronousEventListener,
         return select(pIds, nIds);
     }
 
+    /**
+     * Collects the declared memberships for the given authorizable by traversing the groups structure.
+     *
+     * @param authorizableNodeIdentifier Identifier of the authorizable node
+     * @param session the session to read from
+     * @return a collection of group node ids.
+     * @throws RepositoryException if an error occurs
+     */
     private Collection<String> collectDeclaredMembershipFromTraversal(
             final String authorizableNodeIdentifier, Session session) throws RepositoryException {
 
@@ -340,38 +415,9 @@ public class MembershipCache implements UserConstants, SynchronousEventListener,
         // workaround for failure of Node#getWeakReferences
         // traverse the tree below groups-path and collect membership manually.
         log.info("Traversing groups tree to collect membership.");
-        ItemVisitor visitor = new TraversingItemVisitor.Default() {
-            @Override
-            protected void entering(Property property, int level) throws RepositoryException {
-                PropertyImpl pMember = (PropertyImpl) property;
-                NodeImpl nGroup = (NodeImpl) pMember.getParent();
-                if (P_MEMBERS.equals(pMember.getQName()) && nGroup.isNodeType(NT_REP_GROUP)) {
-                    // Found membership information in members property
-                    for (Value value : property.getValues()) {
-                        String v = value.getString();
-                        if (v.equals(authorizableNodeIdentifier)) {
-                            pIds.add(nGroup.getIdentifier());
-                        }
-                    }
-                } else {
-                    // Found membership information in members node
-                    while (nGroup.isNodeType(NT_REP_MEMBERS)) {
-                        nGroup = (NodeImpl) nGroup.getParent();
-                    }
-
-                    if (nGroup.isNodeType(NT_REP_GROUP) && !NameConstants.JCR_UUID.equals(pMember.getQName())) {
-                        String v = pMember.getString();
-                        if (v.equals(authorizableNodeIdentifier)) {
-                            nIds.add(nGroup.getIdentifier());
-                        }
-                    }
-                }
-            }
-        };
-
         if (session.nodeExists(groupsPath)) {
             Node groupsNode = session.getNode(groupsPath);
-            visitor.visit(groupsNode);
+            traverseAndCollect(authorizableNodeIdentifier, pIds, nIds, (NodeImpl) groupsNode);
         } // else: no groups exist -> nothing to do.
 
         // Based on the user's setting return either of the found membership information
@@ -379,14 +425,87 @@ public class MembershipCache implements UserConstants, SynchronousEventListener,
     }
 
     /**
+     * traverses the groups structure to find the groups of which the given authorizable is member of.
+     *
+     * @param authorizableNodeIdentifier Identifier of the authorizable node
+     * @param pIds output set to update of group node ids that were found via the property memberships
+     * @param nIds output set to update of group node ids that were found via the node memberships
+     * @param node the node to traverse
+     * @throws RepositoryException if an error occurs
+     */
+    private void traverseAndCollect(String authorizableNodeIdentifier, Set<String> pIds, Set<String> nIds, NodeImpl node)
+            throws RepositoryException {
+        if (node.isNodeType(NT_REP_GROUP)) {
+            String groupId = node.getIdentifier();
+            if (node.hasProperty(P_MEMBERS)) {
+                for (Value value : node.getProperty(P_MEMBERS).getValues()) {
+                    String v = value.getString();
+                    if (v.equals(authorizableNodeIdentifier)) {
+                        pIds.add(groupId);
+                    }
+                }
+            }
+            NodeIterator iter = node.getNodes();
+            while (iter.hasNext()) {
+                NodeImpl child = (NodeImpl) iter.nextNode();
+                if (child.isNodeType(NT_REP_MEMBERS)) {
+                    isMemberOfNodeBaseMembershipGroup(authorizableNodeIdentifier, groupId, nIds, child);
+                }
+            }
+        } else {
+            NodeIterator iter = node.getNodes();
+            while (iter.hasNext()) {
+                NodeImpl child = (NodeImpl) iter.nextNode();
+                traverseAndCollect(authorizableNodeIdentifier, pIds, nIds, child);
+            }
+        }
+    }
+
+    /**
+     * traverses the group structure of a node-based group to check if the given authorizable is member of this group.
+     *
+     * @param authorizableNodeIdentifier Identifier of the authorizable node
+     * @param groupId if of the group
+     * @param nIds output set to update of group node ids that were found via the node memberships
+     * @param node the node to traverse
+     * @throws RepositoryException if an error occurs
+     */
+    private void isMemberOfNodeBaseMembershipGroup(String authorizableNodeIdentifier, String groupId, Set<String> nIds,
+                                                   NodeImpl node)
+            throws RepositoryException {
+        PropertyIterator pIter = node.getProperties();
+        while (pIter.hasNext()) {
+            PropertyImpl p = (PropertyImpl) pIter.nextProperty();
+            if (p.getType() == PropertyType.WEAKREFERENCE) {
+                Value[] values = p.isMultiple()
+                        ? p.getValues()
+                        : new Value[]{p.getValue()};
+                for (Value v: values) {
+                    if (v.getString().equals(authorizableNodeIdentifier)) {
+                        nIds.add(groupId);
+                        return;
+                    }
+                }
+            }
+        }
+        NodeIterator iter = node.getNodes();
+        while (iter.hasNext()) {
+            NodeImpl child = (NodeImpl) iter.nextNode();
+            if (child.isNodeType(NT_REP_MEMBERS)) {
+                isMemberOfNodeBaseMembershipGroup(authorizableNodeIdentifier, groupId, nIds, child);
+            }
+        }
+    }
+
+    /**
      * Return either of both sets depending on the users setting whether
      * to use the members property or the members node to record membership
      * information. If both sets are non empty, the one configured in the
      * settings will take precedence and an warning is logged.
      *
-     * @param pIds
-     * @param nIds
-     * @return
+     * @param pIds the set of group node ids retrieved through membership properties
+     * @param nIds the set of group node ids retrieved through membership nodes
+     * @return the selected set.
      */
     private Set<String> select(Set<String> pIds, Set<String> nIds) {
         Set<String> result;
@@ -405,8 +524,7 @@ public class MembershipCache implements UserConstants, SynchronousEventListener,
         }
 
         if (!pIds.isEmpty() && !nIds.isEmpty()) {
-            log.warn("Found members node and members property. Ignoring {} members",
-                    useMembersNode ? "property" : "node");
+            log.warn("Found members node and members property. Ignoring {} members", useMembersNode ? "property" : "node");
         }
 
         return result;
@@ -425,9 +543,13 @@ public class MembershipCache implements UserConstants, SynchronousEventListener,
         }
     }
 
-    private static PropertyIterator getMembershipReferences(String authorizableNodeIdentifier,
-                                                            Session session) {
-
+    /**
+     * Returns the membership references for the given authorizable.
+     * @param authorizableNodeIdentifier Identifier of the authorizable node
+     * @param session session to read from
+     * @return the property iterator or <code>null</code>
+     */
+    private static PropertyIterator getMembershipReferences(String authorizableNodeIdentifier, Session session) {
         PropertyIterator refs = null;
         try {
             refs = session.getNodeByIdentifier(authorizableNodeIdentifier).getWeakReferences(null);
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/PasswordUtility.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/PasswordUtility.java
new file mode 100644
index 0000000..32236b7
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/PasswordUtility.java
@@ -0,0 +1,268 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.core.security.user;
+
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import org.apache.jackrabbit.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility to generate and compare password hashes.
+ */
+public class PasswordUtility {
+
+    private static final Logger log = LoggerFactory.getLogger(PasswordUtility.class);
+
+    private static final char DELIMITER = '-';
+    private static final int NO_ITERATIONS = 1;    
+    private static final String ENCODING = "UTF-8";
+
+    public static final String DEFAULT_ALGORITHM = "SHA-256";
+    public static final int DEFAULT_SALT_SIZE = 8;
+    public static final int DEFAULT_ITERATIONS = 1000;
+
+    /**
+     * Avoid instantiation
+     */
+    private PasswordUtility() {}
+ 
+    /**
+     * Generates a hash of the specified password with the default values
+     * for algorithm, salt-size and number of iterations.
+     *
+     * @param password The password to be hashed.
+     * @return The password hash.
+     * @throws NoSuchAlgorithmException If {@link #DEFAULT_ALGORITHM} is not supported.
+     * @throws UnsupportedEncodingException If utf-8 is not supported.
+     */
+    public static String buildPasswordHash(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException {
+        return buildPasswordHash(password, DEFAULT_ALGORITHM, DEFAULT_SALT_SIZE, DEFAULT_ITERATIONS);
+    }
+
+    /**
+     * Generates a hash of the specified password using the specified algorithm,
+     * salt size and number of iterations into account.
+     *
+     * @param password The password to be hashed.
+     * @param algorithm The desired hash algorithm.
+     * @param saltSize The desired salt size. If the specified integer is lower
+     * that {@link #DEFAULT_SALT_SIZE} the default is used.
+     * @param iterations The desired number of iterations. If the specified
+     * integer is lower than 1 the {@link #DEFAULT_ITERATIONS default} value is used.
+     * @return  The password hash.
+     * @throws NoSuchAlgorithmException If the specified algorithm is not supported.
+     * @throws UnsupportedEncodingException If utf-8 is not supported.
+     */
+    public static String buildPasswordHash(String password, String algorithm,
+                                           int saltSize, int iterations) throws NoSuchAlgorithmException, UnsupportedEncodingException {
+        if (password == null) {
+            throw new IllegalArgumentException("Password may not be null.");
+        }
+        if (iterations < NO_ITERATIONS) {
+            iterations = DEFAULT_ITERATIONS;
+        }
+        if (saltSize < DEFAULT_SALT_SIZE) {
+            saltSize = DEFAULT_SALT_SIZE;
+        }
+        String salt = generateSalt(saltSize);
+        String alg = (algorithm == null) ? DEFAULT_ALGORITHM : algorithm;
+        return generateHash(password, alg, salt, iterations);
+    }
+
+    /**
+     * Returns {@code true} if the specified string doesn't start with a
+     * valid algorithm name in curly brackets.
+     *
+     * @param password The string to be tested.
+     * @return {@code true} if the specified string doesn't start with a
+     * valid algorithm name in curly brackets.
+     */
+    public static boolean isPlainTextPassword(String password) {
+        return extractAlgorithm(password) == null;
+    }
+
+    /**
+     * Returns {@code true} if hash of the specified {@code password} equals the
+     * given hashed password.
+     *
+     * @param hashedPassword Password hash.
+     * @param password The password to compare.
+     * @return If the hash of the specified {@code password} equals the given
+     * {@code hashedPassword} string.
+     */
+    public static boolean isSame(String hashedPassword, String password) {
+        try {
+            String algorithm = extractAlgorithm(hashedPassword);
+            if (algorithm != null) {
+                int startPos = algorithm.length()+2;
+                String salt = extractSalt(hashedPassword, startPos);
+                int iterations = NO_ITERATIONS;
+                if (salt != null) {
+                    startPos += salt.length()+1;
+                    iterations = extractIterations(hashedPassword, startPos);
+                }
+
+                String hash = generateHash(password, algorithm, salt, iterations);
+                return compareSecure(hashedPassword, hash);
+            } // hashedPassword is plaintext -> return false
+        } catch (NoSuchAlgorithmException e) {
+            log.warn(e.getMessage());
+        } catch (UnsupportedEncodingException e) {
+            log.warn(e.getMessage());
+        }
+        return false;
+    }
+
+    /**
+     * Extract the algorithm from the given crypted password string. Returns the
+     * algorithm or {@code null} if the given string doesn't have a
+     * leading {@code algorithm} such as created by {@code buildPasswordHash}
+     * or if the extracted string doesn't represent an available algorithm.
+     *
+     * @param hashedPwd The password hash.
+     * @return The algorithm or {@code null} if the given string doesn't have a
+     * leading {@code algorithm} such as created by {@code buildPasswordHash}
+     * or if the extracted string isn't a supported algorithm.
+     */
+    public static String extractAlgorithm(String hashedPwd) {
+        if (hashedPwd != null && hashedPwd.length() > 0) {
+            int end = hashedPwd.indexOf('}');
+            if (hashedPwd.charAt(0) == '{' && end > 0 && end < hashedPwd.length()-1) {
+                String algorithm = hashedPwd.substring(1, end);
+                try {
+                    MessageDigest.getInstance(algorithm);
+                    return algorithm;
+                } catch (NoSuchAlgorithmException e) {
+                    log.debug("Invalid algorithm detected " + algorithm);
+                }
+            }
+        }
+
+        // not starting with {} or invalid algorithm
+        return null;
+    }
+
+    //------------------------------------------------------------< private >---
+
+    private static String generateHash(String pwd, String algorithm, String salt, int iterations) throws NoSuchAlgorithmException, UnsupportedEncodingException {
+        StringBuilder passwordHash = new StringBuilder();
+        passwordHash.append('{').append(algorithm).append('}');
+        if (salt != null && salt.length() > 0) {
+            StringBuilder data = new StringBuilder();
+            data.append(salt).append(pwd);
+
+            passwordHash.append(salt).append(DELIMITER);
+            if (iterations > NO_ITERATIONS) {
+                passwordHash.append(iterations).append(DELIMITER);
+            }
+            passwordHash.append(generateDigest(data.toString(), algorithm, iterations));
+        } else {
+            // backwards compatible to jr 2.0: no salt, no iterations
+            passwordHash.append(Text.digest(algorithm, pwd.getBytes(ENCODING)));
+        }
+        return passwordHash.toString();
+    }
+
+    private static String generateSalt(int saltSize) {
+        SecureRandom random = new SecureRandom();
+        byte[] salt = new byte[saltSize];
+        random.nextBytes(salt);
+
+        StringBuilder res = new StringBuilder(salt.length * 2);
+        for (byte b : salt) {
+            res.append(Text.hexTable[(b >> 4) & 15]);
+            res.append(Text.hexTable[b & 15]);
+        }
+        return res.toString();
+    }
+
+    private static String generateDigest(String data, String algorithm, int iterations) throws UnsupportedEncodingException, NoSuchAlgorithmException {
+        byte[] bytes = data.getBytes(ENCODING);
+        MessageDigest md = MessageDigest.getInstance(algorithm);
+
+        for (int i = 0; i < iterations; i++) {
+            md.reset();
+            bytes = md.digest(bytes);
+        }
+
+        StringBuilder res = new StringBuilder(bytes.length * 2);
+        for (byte b : bytes) {
+            res.append(Text.hexTable[(b >> 4) & 15]);
+            res.append(Text.hexTable[b & 15]);
+        }
+        return res.toString();
+    }
+    
+    private static String extractSalt(String hashedPwd, int start) {
+        int end = hashedPwd.indexOf(DELIMITER, start);
+        if (end > -1) {
+            return hashedPwd.substring(start, end);
+        }
+        // no salt
+        return null;
+    }
+
+    private static int extractIterations(String hashedPwd, int start) {
+        int end = hashedPwd.indexOf(DELIMITER, start);
+        if (end > -1) {
+            String str = hashedPwd.substring(start, end);
+            try {
+                return Integer.parseInt(str);
+            } catch (NumberFormatException e) {
+                log.debug("Expected number of iterations. Found: " + str);
+            }
+        }
+
+        // no extra iterations
+        return NO_ITERATIONS;
+    }
+
+    /**
+     * Compare two strings. The comparison is constant time: it will always loop
+     * over all characters and doesn't use conditional operations in the loop to
+     * make sure an attacker can not use a timing attack.
+     *
+     * @param a
+     * @param b
+     * @return true if both parameters contain the same data.
+     */
+    private static boolean compareSecure(String a, String b) {
+        if ((a == null) || (b == null)) {
+            return (a == null) && (b == null);
+        }
+        int len = a.length();
+        if (len != b.length()) {
+            return false;
+        }
+        if (len == 0) {
+            return true;
+        }
+        // don't use conditional operations inside the loop
+        int bits = 0;
+        for (int i = 0; i < len; i++) {
+            // this will never reset any bits
+            bits |= a.charAt(i) ^ b.charAt(i);
+        }
+        return bits == 0;
+    }
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserAccessControlProvider.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserAccessControlProvider.java
index 96cc5fc..7ea7a53 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserAccessControlProvider.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserAccessControlProvider.java
@@ -64,7 +64,7 @@ import java.util.Set;
  * is used to protected the 'security workspace' containing the user and
  * group data. It applies special care to make sure that modifying user data
  * (e.g. password), group membership and impersonation is properly controlled.
- * <p/>
+ * <p>
  * This provider creates upon initialization the following 2 groups:
  * <ul>
  * <li>User administrator</li>
@@ -182,7 +182,7 @@ public class UserAccessControlProvider extends AbstractAccessControlProvider
             usersPath = (uMgr instanceof UserManagerImpl) ? ((UserManagerImpl) uMgr).getUsersPath() : UserConstants.USERS_PATH;
             groupsPath = (uMgr instanceof UserManagerImpl) ? ((UserManagerImpl) uMgr).getGroupsPath() : UserConstants.GROUPS_PATH;
 
-            membersInProperty = (!(uMgr instanceof UserManagerImpl)) || ((UserManagerImpl) uMgr).getGroupMembershipSplitSize() <= 0;
+            membersInProperty = !(uMgr instanceof UserManagerImpl) || !((UserManagerImpl) uMgr).hasMemberSplitSize();
 
             if (configuration.containsKey(PARAM_ANONYMOUS_ID)) {
                 anonymousId = (String) configuration.get(PARAM_ANONYMOUS_ID);
@@ -278,7 +278,7 @@ public class UserAccessControlProvider extends AbstractAccessControlProvider
             }
         } catch (RepositoryException e) {
             // should never get here
-            log.error("Internal error while retrieving user principal", e.getMessage());
+            log.error("Internal error while retrieving user principal: {}", e.getMessage());
         }
         // none of the principals in the set is assigned to a User.
         return null;
@@ -291,7 +291,7 @@ public class UserAccessControlProvider extends AbstractAccessControlProvider
                 String path = principal.getPath();
                 userNode = (NodeImpl) session.getNode(path);
             } catch (RepositoryException e) {
-                log.warn("Error while retrieving user node.", e.getMessage());
+                log.warn("Error while retrieving user node. {}", e.getMessage());
             }
         }
         return userNode;
@@ -343,7 +343,7 @@ public class UserAccessControlProvider extends AbstractAccessControlProvider
             }
         } catch (RepositoryException e) {
             // should never get here
-            log.error("Error while initializing user/group administrators", e.getMessage());
+            log.error("Error while initializing user/group administrators: ()", e.getMessage());
         }
         return null;
     }
@@ -527,7 +527,7 @@ public class UserAccessControlProvider extends AbstractAccessControlProvider
             try {
                 observationMgr.removeEventListener(this);
             } catch (RepositoryException e) {
-                log.error("Internal error: ", e.getMessage());
+                log.error("Internal error: {}", e.getMessage());
             }
             super.close();
         }
@@ -592,7 +592,7 @@ public class UserAccessControlProvider extends AbstractAccessControlProvider
                     } // else: not interested.
                 } catch (RepositoryException e) {
                     // should never get here
-                    log.warn("Internal error ", e.getMessage());
+                    log.warn("Internal error: {}", e.getMessage());
                     clearCache();
                 }
             }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImpl.java
index a6e98df..a76fa72 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImpl.java
@@ -26,7 +26,6 @@ import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
 
 import javax.jcr.Credentials;
 import javax.jcr.RepositoryException;
-import javax.jcr.SimpleCredentials;
 import javax.jcr.Value;
 import java.io.UnsupportedEncodingException;
 import java.security.NoSuchAlgorithmException;
@@ -44,24 +43,6 @@ public class UserImpl extends AuthorizableImpl implements User {
         super(node, userManager);
     }
 
-    //--------------------------------------------------------------------------
-    /**
-     * Creates a hash of the specified password if it is found to be plain text.
-     * 
-     * @param password
-     * @return
-     * @throws RepositoryException
-     */
-    static String buildPasswordValue(String password) throws RepositoryException {
-        try {
-            return new CryptedSimpleCredentials("_", password).getPassword();
-        } catch (NoSuchAlgorithmException e) {
-            throw new RepositoryException(e);
-        } catch (UnsupportedEncodingException e) {
-            throw new RepositoryException(e);
-        }
-    }
-
     //-------------------------------------------------------< Authorizable >---
     /**
      * @see org.apache.jackrabbit.api.security.user.Authorizable#isGroup()
@@ -98,6 +79,10 @@ public class UserImpl extends AuthorizableImpl implements User {
         }
     }
 
+    public boolean isSystemUser() {
+        return false;
+    }
+
     /**
      * @see User#getCredentials()
      */
@@ -127,8 +112,10 @@ public class UserImpl extends AuthorizableImpl implements User {
      */
     public void changePassword(String password) throws RepositoryException {
         userManager.onPasswordChange(this, password);
-        Value v = getSession().getValueFactory().createValue(buildPasswordValue(password));
-        userManager.setProtectedProperty(getNode(), P_PASSWORD, v);
+        userManager.setPassword(getNode(), password, true);
+        if (userManager.isAutoSave()) {
+            getNode().save();
+        }
     }
 
     /**
@@ -136,18 +123,10 @@ public class UserImpl extends AuthorizableImpl implements User {
      */
     public void changePassword(String password, String oldPassword) throws RepositoryException {
         // make sure the old password matches.
-        try {
-            CryptedSimpleCredentials csc = (CryptedSimpleCredentials) getCredentials();
-            SimpleCredentials creds = new SimpleCredentials(getID(), oldPassword.toCharArray());
-            if (!csc.matches(creds)) {
-                throw new RepositoryException("Failed to change password: Old password does not match.");
-            }
-        } catch (NoSuchAlgorithmException e) {
-            throw new RepositoryException("Cannot change password: failed to validate old password.");
-        } catch (UnsupportedEncodingException e) {
-            throw new RepositoryException("Cannot change password: failed to validate old password.");
+        String pwHash = getNode().getProperty(P_PASSWORD).getString();
+        if (!PasswordUtility.isSame(pwHash, oldPassword)) {
+            throw new RepositoryException("Failed to change password: Old password does not match.");
         }
-
         changePassword(password);
     }
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImporter.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImporter.java
index bec7483..8f26372 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImporter.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserImporter.java
@@ -68,7 +68,7 @@ import org.slf4j.LoggerFactory;
  * imported content using {@link javax.jcr.Session#save()}. Omitting the
  * save call will lead to transient, semi-validated user content and eventually
  * to inconsistencies.
- * <p/>
+ * <p>
  * Note the following restrictions:
  * <ul>
  * <li>The importer will only be initialized if the user manager is an instance
@@ -140,6 +140,17 @@ public class UserImporter implements ProtectedPropertyImporter, ProtectedNodeImp
 
     private int importBehavior = ImportBehavior.IGNORE;
 
+    /**
+     * Container used to collect group members stored in protected nodes.
+     */
+    private Membership currentMembership;
+
+    /**
+     * Temporary store for the pw an imported new user to be able to call
+     * the creation actions irrespective of the order of protected properties
+     */
+    private Map<String,String> currentPw = new HashMap<String,String>(1);
+
     public boolean init(JackrabbitSession session, NamePathResolver resolver,
                         boolean isWorkspaceImport,
                         int uuidBehavior, ReferenceChangeTracker referenceTracker) {
@@ -182,8 +193,10 @@ public class UserImporter implements ProtectedPropertyImporter, ProtectedNodeImp
         return initialized;
     }
 
-    // -----------------------------------------------------< ProtectedPropertyImporter >---
-
+    // -----------------------------------------< ProtectedPropertyImporter >---
+    /**
+     * @see ProtectedPropertyImporter#handlePropInfo(org.apache.jackrabbit.core.NodeImpl, org.apache.jackrabbit.core.xml.PropInfo, org.apache.jackrabbit.spi.QPropertyDefinition)
+     */
     public boolean handlePropInfo(NodeImpl parent, PropInfo protectedPropInfo, QPropertyDefinition def) throws RepositoryException {
         if (!initialized) {
             throw new IllegalStateException("Not initialized");
@@ -222,6 +235,22 @@ public class UserImporter implements ProtectedPropertyImporter, ProtectedNodeImp
                 Value v = protectedPropInfo.getValues(PropertyType.STRING, resolver)[0];
                 String princName = v.getString();
                 userManager.setPrincipal(parent, new PrincipalImpl(princName));
+
+                /*
+                Execute authorizable actions for a NEW group as this is the
+                same place in the userManager#createGroup that the actions
+                are called.
+                In case of a NEW user the actions are executed if the password
+                has been imported before.
+                */
+                if (parent.isNew()) {
+                    if (a.isGroup()) {
+                        userManager.onCreate((Group) a);
+                    } else if (currentPw.containsKey(a.getID())) {
+                        userManager.onCreate((User) a, currentPw.remove(a.getID()));
+                    }
+                }
+
                 return true;
             } else if (UserConstants.P_PASSWORD.equals(propName)) {
                 if (a.isGroup()) {
@@ -236,8 +265,23 @@ public class UserImporter implements ProtectedPropertyImporter, ProtectedNodeImp
                 }
 
                 Value v = protectedPropInfo.getValues(PropertyType.STRING, resolver)[0];
-                ((User) a).changePassword(v.getString());
-
+                String pw = v.getString();
+                userManager.setPassword(parent, pw, false);
+
+                /*
+                 Execute authorizable actions for a NEW user at this point after
+                 having set the password if the principal name has already been
+                 processed, otherwise postpone it.
+                 */
+                if (parent.isNew()) {
+                    if (parent.hasProperty(UserConstants.P_PRINCIPAL_NAME)) {
+                        userManager.onCreate((User) a, pw);
+                    } else {
+                        // principal name not yet available -> remember the pw
+                        currentPw.clear();
+                        currentPw.put(a.getID(), pw);
+                    }
+                }
                 return true;
 
             } else if (UserConstants.P_IMPERSONATORS.equals(propName)) {
@@ -314,10 +358,16 @@ public class UserImporter implements ProtectedPropertyImporter, ProtectedNodeImp
         }
     }
 
+    /**
+     * @see ProtectedPropertyImporter#handlePropInfo(org.apache.jackrabbit.core.NodeImpl, org.apache.jackrabbit.core.xml.PropInfo, org.apache.jackrabbit.spi.QPropertyDefinition)
+     */
     public boolean handlePropInfo(NodeState parent, PropInfo protectedPropInfo, QPropertyDefinition def) throws RepositoryException {
         return false;
     }
 
+    /**
+     * @see org.apache.jackrabbit.core.xml.ProtectedPropertyImporter#processReferences()
+     */
     public void processReferences() throws RepositoryException {
         if (!initialized) {
             throw new IllegalStateException("Not initialized");
@@ -390,7 +440,7 @@ public class UserImporter implements ProtectedPropertyImporter, ProtectedNodeImp
                         log.info("ImportBehavior.BESTEFFORT: Found " + nonExisting.size() + " entries of rep:members pointing to non-existing authorizables. Adding to rep:members.");
                         final NodeImpl groupNode = ((AuthorizableImpl) gr).getNode();
 
-                        if (userManager.getGroupMembershipSplitSize() > 0) {
+                        if (userManager.hasMemberSplitSize()) {
                             userManager.performProtectedOperation((SessionImpl) session, new SessionWriteOperation<Object>() {
                                 public Boolean perform(SessionContext context) throws RepositoryException {
                                     NodeImpl nMembers = (groupNode.hasNode(UserConstants.N_MEMBERS)
@@ -494,31 +544,16 @@ public class UserImporter implements ProtectedPropertyImporter, ProtectedNodeImp
         }
     }
 
-    //------------------------------------------------------------< private >---
-    private void handleFailure(String msg) throws RepositoryException {
-        switch (importBehavior) {
-            case ImportBehavior.IGNORE:
-            case ImportBehavior.BESTEFFORT:
-                log.warn(msg);
-                break;
-            case ImportBehavior.ABORT:
-                throw new ConstraintViolationException(msg);
-            default:
-                // no other behavior. nothing to do.
-
-        }
-    }
-
-    // -----------------------------------------------------< ProtectedNodeImporter >---
-
-    private Membership currentMembership;
-
+    // ---------------------------------------------< ProtectedNodeImporter >---
+    /**
+     * @see ProtectedNodeImporter#start(org.apache.jackrabbit.core.NodeImpl)
+     */
     public boolean start(NodeImpl protectedParent) throws RepositoryException {
         String repMembers = resolver.getJCRName(UserConstants.NT_REP_MEMBERS);
         if (repMembers.equals(protectedParent.getPrimaryNodeType().getName())) {
             NodeImpl groupNode = protectedParent;
-            while(groupNode.getDepth() != 0 &&
-                  repMembers.equals(groupNode.getPrimaryNodeType().getName())) {
+            while (groupNode.getDepth() != 0 &&
+                    repMembers.equals(groupNode.getPrimaryNodeType().getName())) {
 
                 groupNode = (NodeImpl) groupNode.getParent();
             }
@@ -526,21 +561,25 @@ public class UserImporter implements ProtectedPropertyImporter, ProtectedNodeImp
             if (auth == null) {
                 log.debug("Cannot handle protected node " + protectedParent + ". It nor one of its parents represent a valid Authorizable.");
                 return false;
-            }
-            else {
+            } else {
                 currentMembership = new Membership(auth.getID());
                 return true;
             }
-        }
-        else {
+        } else {
             return false;
         }
     }
 
+    /**
+     * @see ProtectedNodeImporter#start(org.apache.jackrabbit.core.state.NodeState)
+     */
     public boolean start(NodeState protectedParent) {
         return false;
     }
 
+    /**
+     * @see ProtectedNodeImporter#start(org.apache.jackrabbit.core.NodeImpl)
+     */
     public void startChildInfo(NodeInfo childInfo, List<PropInfo> propInfos) throws RepositoryException {
         assert (currentMembership != null);
 
@@ -552,20 +591,28 @@ public class UserImporter implements ProtectedPropertyImporter, ProtectedNodeImp
                     currentMembership.addMember(name, id);
                 }
             }
-        }
-        else {
+        } else {
             log.warn("{} is not of type {}", childInfo.getName(), UserConstants.NT_REP_MEMBERS);
         }
     }
 
+    /**
+     * @see org.apache.jackrabbit.core.xml.ProtectedNodeImporter#endChildInfo()
+     */
     public void endChildInfo() throws RepositoryException {
     }
 
+    /**
+     * @see ProtectedNodeImporter#end(org.apache.jackrabbit.core.NodeImpl)
+     */
     public void end(NodeImpl protectedParent) throws RepositoryException {
         referenceTracker.processedReference(currentMembership);
         currentMembership = null;
     }
 
+    /**
+     * @see ProtectedNodeImporter#end(org.apache.jackrabbit.core.state.NodeState)
+     */
     public void end(NodeState protectedParent) {
     }
 
@@ -585,7 +632,28 @@ public class UserImporter implements ProtectedPropertyImporter, ProtectedNodeImp
         this.importBehavior = ImportBehavior.valueFromName(importBehaviorStr);
     }
 
-    //--------------------------------------------------------------------------
+    //------------------------------------------------------------< private >---
+    /**
+     * Handling the import behavior
+     *
+     * @param msg
+     * @throws RepositoryException
+     */
+    private void handleFailure(String msg) throws RepositoryException {
+        switch (importBehavior) {
+            case ImportBehavior.IGNORE:
+            case ImportBehavior.BESTEFFORT:
+                log.warn(msg);
+                break;
+            case ImportBehavior.ABORT:
+                throw new ConstraintViolationException(msg);
+            default:
+                // no other behavior. nothing to do.
+
+        }
+    }
+
+    //------------------------------------------------------< inner classes >---
     /**
      * Inner class used to postpone import of group membership to the very end
      * of the import. This allows to import membership of user/groups that
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerConfig.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerConfig.java
new file mode 100644
index 0000000..1cf0c72
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerConfig.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.security.user;
+
+import org.apache.jackrabbit.core.security.user.action.AuthorizableAction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Properties;
+
+/**
+ * Utility to retrieve configuration parameters for UserManagerImpl
+ */
+class UserManagerConfig {
+
+    private static final Logger log = LoggerFactory.getLogger(UserManagerImpl.class);
+
+    private final Properties config;
+    private final String adminId;
+    /**
+     * Authorizable actions that will all be executed upon creation and removal
+     * of authorizables in the order they are contained in the array.<p/>
+     * Note, that if {@link #isAutoSave() autosave} is turned on, the configured
+     * actions are executed before persisting the creation or removal.
+     */
+    private AuthorizableAction[] actions;
+
+    UserManagerConfig(Properties config, String adminId, AuthorizableAction[] actions) {
+        this.config = config;
+        this.adminId = adminId;
+        this.actions = (actions == null) ? new AuthorizableAction[0] : actions;
+    }
+
+    public <T> T getConfigValue(String key, T defaultValue) {
+        if (config != null && config.containsKey(key)) {
+            return convert(config.get(key), defaultValue);
+        } else {
+            return defaultValue;
+        }
+    }
+
+    public String getAdminId() {
+        return adminId;
+    }
+
+    public AuthorizableAction[] getAuthorizableActions() {
+        return actions;
+    }
+
+    public void setAuthorizableActions(AuthorizableAction[] actions) {
+        if (actions != null) {
+            this.actions = actions;
+        }
+    }
+
+    //--------------------------------------------------------< private >---
+    private <T> T convert(Object v, T defaultValue) {
+        if (v == null) {
+            return null;
+        }
+
+        T value;
+        String str = v.toString();
+        Class targetClass = (defaultValue == null) ? String.class : defaultValue.getClass();
+        try {
+            if (targetClass == String.class) {
+                value = (T) str;
+            } else if (targetClass == Integer.class) {
+                value = (T) Integer.valueOf(str);
+            } else if (targetClass == Long.class) {
+                value = (T) Long.valueOf(str);
+            } else if (targetClass == Double.class) {
+                value = (T) Double.valueOf(str);
+            } else if (targetClass == Boolean.class) {
+                value = (T) Boolean.valueOf(str);
+            } else {
+                // unsupported target type
+                log.warn("Unsupported target type {} for value {}", targetClass.getName(), v);
+                throw new IllegalArgumentException("Cannot convert config entry " + v + " to " + targetClass.getName());
+            }
+        } catch (NumberFormatException e) {
+            log.warn("Invalid value {}; cannot be parsed into {}", v, targetClass.getName());
+            value = defaultValue;
+        }
+
+        return value;
+    }
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java
index b9f82f0..6d6b912 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserManagerImpl.java
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.core.security.user;
 import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.AuthorizableExistsException;
+import org.apache.jackrabbit.api.security.user.AuthorizableTypeException;
 import org.apache.jackrabbit.api.security.user.Group;
 import org.apache.jackrabbit.api.security.user.Query;
 import org.apache.jackrabbit.api.security.user.User;
@@ -51,6 +52,7 @@ import javax.jcr.lock.LockException;
 import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.version.VersionException;
 import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
 import java.security.Principal;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -60,6 +62,8 @@ import java.util.Set;
 import java.util.UUID;
 
 /**
+ * <h2>Implementation Characteristics</h2>
+ *
  * Default implementation of the <code>UserManager</code> interface with the
  * following characteristics:
  *
@@ -78,6 +82,8 @@ import java.util.UUID;
  * </li>
  * </ul>
  *
+ * <h3>Authorizable Creation</h3>
+ *
  * The built-in logic applies the following rules:
  * <ul>
  * <li>The names of the hierarchy folders is determined from ID of the
@@ -115,9 +121,12 @@ import java.util.UUID;
  *           + aSmith        [rep:User]
  * </pre>
  *
+ * <h3>Configuration</h3>
+ *
  * This <code>UserManager</code> is able to handle the following configuration
  * options:
  *
+ * <h4>Configuration Parameters</h4>
  * <ul>
  * <li>{@link #PARAM_USERS_PATH}: Defines where user nodes are created.
  * If missing set to {@link #USERS_PATH}.</li>
@@ -142,7 +151,25 @@ import java.util.UUID;
  * <li>{@link #PARAM_AUTO_EXPAND_SIZE}: This parameter only takes effect
  * if {@link #PARAM_AUTO_EXPAND_TREE} is enabled.<br>The value is expected to be
  * a positive long greater than zero. The default value is 1000.</li>
+ * <li>{@link #PARAM_GROUP_MEMBERSHIP_SPLIT_SIZE}: If this parameter is present
+ * group memberships are collected in a node structure below {@link UserConstants#N_MEMBERS}
+ * instead of the default multi valued property {@link UserConstants#P_MEMBERS}.
+ * Its value determines the maximum number of member properties until additional
+ * intermediate nodes are inserted. Valid parameter values are integers > 4.</li>
+ * <li>{@link #PARAM_PASSWORD_HASH_ALGORITHM}: Optional parameter to configure
+ * the algorithm used for password hash generation. The default value is
+ * {@link PasswordUtility#DEFAULT_ALGORITHM}.</li>
+ * <li>{@link #PARAM_PASSWORD_HASH_ITERATIONS}: Optional parameter to configure
+ * the number of iterations used for password hash generations. The default
+ * value is {@link PasswordUtility#DEFAULT_ITERATIONS}.</li>
  * </ul>
+ *
+ * <h4>Authorizable Actions</h4>
+ * In addition to the specified configuration parameters this user manager
+ * implementation allows to define zero to many {@link AuthorizableAction}s.
+ * Authorizable actions provide the ability to execute additional validation or
+ * tasks upon authorizable creation, removal and upon changing a users password.<br/>
+ * See also {@link org.apache.jackrabbit.core.config.UserManagerConfig#getAuthorizableActions()}
  */
 public class UserManagerImpl extends ProtectedItemModifier
         implements UserManager, UserConstants, SessionListener {
@@ -179,7 +206,7 @@ public class UserManagerImpl extends ProtectedItemModifier
     /**
      * Parameter used to change the number of levels that are used by default
      * store authorizable nodes.<br>The default number of levels is 2.
-     * <p/>
+     * <p>
      * <strong>NOTE:</strong> Changing the default depth once users and groups
      * have been created in the repository will cause inconsistencies, due to
      * the fact that the resolution of ID to an authorizable relies on the
@@ -212,65 +239,41 @@ public class UserManagerImpl extends ProtectedItemModifier
     public static final String PARAM_AUTO_EXPAND_SIZE = "autoExpandSize";
 
     /**
-     * If this parameter is present group memberships are collected in a node
+     * If this parameter is present group members are collected in a node
      * structure below {@link UserConstants#N_MEMBERS} instead of the default
      * multi valued property {@link UserConstants#P_MEMBERS}. Its value determines
      * the maximum number of member properties until additional intermediate nodes
-     * are inserted. Valid values are integers > 4.
+     * are inserted. Valid values are integers > 4. The default value is 0 and
+     * indicates that the {@link UserConstants#P_MEMBERS} property is used to
+     * record group members.
      */
     public static final String PARAM_GROUP_MEMBERSHIP_SPLIT_SIZE = "groupMembershipSplitSize";
 
+    /**
+     * Configuration parameter to change the default algorithm used to generate
+     * password hashes. The default value is {@link PasswordUtility#DEFAULT_ALGORITHM}.
+     */
+    public static final String PARAM_PASSWORD_HASH_ALGORITHM = "passwordHashAlgorithm";
+
+    /**
+     * Configuration parameter to change the number of iterations used for
+     * password hash generation. The default value is {@link PasswordUtility#DEFAULT_ITERATIONS}.
+     */
+    public static final String PARAM_PASSWORD_HASH_ITERATIONS = "passwordHashIterations";
+
     private static final Logger log = LoggerFactory.getLogger(UserManagerImpl.class);
 
     private final SessionImpl session;
     private final String adminId;
     private final NodeResolver authResolver;
     private final NodeCreator nodeCreator;
+    private final UserManagerConfig config;
 
-    /**
-     * Configuration value defining the node where User nodes will be created.
-     * Default value is {@link UserConstants#USERS_PATH}.
-     */
     private final String usersPath;
-
-    /**
-     * Configuration value defining the node where Group nodes will be created.
-     * Default value is {@link UserConstants#GROUPS_PATH}.
-     */
     private final String groupsPath;
-
-    /**
-     * Flag indicating if {@link #getAuthorizable(String)} should be able to deal
-     * with users or groups created with Jackrabbit < 2.0.<br>
-     * As of 2.0 authorizables are created using a defined logic that allows
-     * to retrieve them without searching/traversing. If this flag is
-     * <code>true</code> this method will try to find authorizables using the
-     * <code>authResolver</code> if not found otherwise.
-     */
-    private final boolean compatibleJR16;
-
-    /**
-     * Maximum number of properties on the group membership node structure under
-     * {@link UserConstants#N_MEMBERS} until additional intermediate nodes are inserted.
-     * If 0 (default), {@link UserConstants#P_MEMBERS} is used to record group
-     * memberships.
-     */
-    private final int groupMembershipSplitSize;
-
-    /**
-     * The membership cache.
-     */
     private final MembershipCache membershipCache;
 
     /**
-     * Authorizable actions that will all be executed upon creation and removal
-     * of authorizables in the order they are contained in the array.<p/>
-     * Note, that if {@link #isAutoSave() autosave} is turned on, the configured
-     * actions are executed before persisting the creation or removal.
-     */
-    private AuthorizableAction[] authorizableActions = new AuthorizableAction[0];
-
-    /**
      * Create a new <code>UserManager</code> with the default configuration.
      *
      * @param session The editing/reading session.
@@ -317,27 +320,31 @@ public class UserManagerImpl extends ProtectedItemModifier
      */
     public UserManagerImpl(SessionImpl session, String adminId, Properties config,
                            MembershipCache mCache) throws RepositoryException {
+        this(session, new UserManagerConfig(config, adminId, null), mCache);
+    }
+
+    /**
+     * Create a new <code>UserManager</code> for the given <code>session</code>.
+     *
+     * @param session The editing/reading session.
+     * @param config The user manager configuration.
+     * @param mCache The shared membership cache.
+     * @throws RepositoryException If an error occurs.
+     */
+    private UserManagerImpl(SessionImpl session, UserManagerConfig config, MembershipCache mCache) throws RepositoryException {
         this.session = session;
-        this.adminId = adminId;
+        this.adminId = config.getAdminId();
+        this.config = config;
 
         nodeCreator = new NodeCreator(config);
 
-        Object param = (config != null) ? config.get(PARAM_USERS_PATH) : null;
-        usersPath = (param != null) ? param.toString() : USERS_PATH;
-
-        param = (config != null) ? config.get(PARAM_GROUPS_PATH) : null;
-        groupsPath = (param != null) ? param.toString() : GROUPS_PATH;
-
-        param = (config != null) ? config.get(PARAM_COMPATIBLE_JR16) : null;
-        compatibleJR16 = (param != null) && Boolean.parseBoolean(param.toString());
-
-        param = (config != null) ? config.get(PARAM_GROUP_MEMBERSHIP_SPLIT_SIZE) : null;
-        groupMembershipSplitSize = parseMembershipSplitSize(param);
+        this.usersPath = config.getConfigValue(PARAM_USERS_PATH, USERS_PATH);
+        this.groupsPath = config.getConfigValue(PARAM_GROUPS_PATH, GROUPS_PATH);
 
         if (mCache != null) {
             membershipCache = mCache;
         } else {
-            membershipCache = new MembershipCache(session, groupsPath, groupMembershipSplitSize > 0);
+            membershipCache = new MembershipCache(session, groupsPath, hasMemberSplitSize());
         }
 
         NodeResolver nr;
@@ -388,8 +395,25 @@ public class UserManagerImpl extends ProtectedItemModifier
      *
      * @return The maximum number of group members before splitting up the structure.
      */
-    public int getGroupMembershipSplitSize() {
-        return groupMembershipSplitSize;
+    public int getMemberSplitSize() {
+        int splitSize = config.getConfigValue(PARAM_GROUP_MEMBERSHIP_SPLIT_SIZE, 0);
+        if (splitSize != 0 && splitSize < 4) {
+            log.warn("Invalid value {} for {}. Expected integer >= 4", splitSize, PARAM_GROUP_MEMBERSHIP_SPLIT_SIZE);
+            splitSize = 0;
+        }
+        return splitSize;
+    }
+
+    /**
+     * Returns <code>true</code> if the split-member configuration parameter
+     * is greater or equal than 4 indicating that group members should be stored
+     * in a tree instead of a single multivalued property.
+     *
+     * @return true if group members are being stored in a tree instead of a
+     * single multivalued property.
+     */
+    public boolean hasMemberSplitSize() {
+        return getMemberSplitSize() >= 4;
     }
 
     /**
@@ -399,9 +423,7 @@ public class UserManagerImpl extends ProtectedItemModifier
      * @param authorizableActions An array of authorizable actions.
      */
     public void setAuthorizableActions(AuthorizableAction[] authorizableActions) {
-        if (authorizableActions != null) {
-            this.authorizableActions = authorizableActions;
-        }
+        config.setAuthorizableActions(authorizableActions);
     }
 
     //--------------------------------------------------------< UserManager >---
@@ -430,6 +452,22 @@ public class UserManagerImpl extends ProtectedItemModifier
     }
 
     /**
+     * @see UserManager#getAuthorizable(String, Class)
+     */
+    public <T extends Authorizable> T getAuthorizable(String id, Class<T> authorizableClass) throws AuthorizableTypeException, RepositoryException {
+        Authorizable authorizable = getAuthorizable(id);
+        if (authorizable == null) {
+            return null;
+        } else {
+            if (authorizableClass != null && authorizableClass.isInstance(authorizable)) {
+                return authorizableClass.cast(authorizable);
+            } else {
+                throw new AuthorizableTypeException("Invalid authorizable type for authorizable '" + id + "'");
+            }
+        }
+    }
+
+    /**
      * @see UserManager#getAuthorizable(Principal)
      */
     public Authorizable getAuthorizable(Principal principal) throws RepositoryException {
@@ -548,15 +586,14 @@ public class UserManagerImpl extends ProtectedItemModifier
                            Principal principal, String intermediatePath)
             throws AuthorizableExistsException, RepositoryException {
         checkValidID(userID);
-        if (password == null) {
-            throw new IllegalArgumentException("Cannot create user: null password.");
-        }
+
+        // NOTE: password validation during setPassword and onCreate.
         // NOTE: principal validation during setPrincipal call.
 
         try {
             NodeImpl userNode = (NodeImpl) nodeCreator.createUserNode(userID, intermediatePath);
             setPrincipal(userNode, principal);
-            setProperty(userNode, P_PASSWORD, getValue(UserImpl.buildPasswordValue(password)), true);
+            setPassword(userNode, password, true);
 
             User user = createUser(userNode);
             onCreate(user, password);
@@ -574,11 +611,14 @@ public class UserManagerImpl extends ProtectedItemModifier
         }
     }
 
+    public User createSystemUser(String userID, String intermediatePath) throws AuthorizableExistsException, RepositoryException {
+        throw new UnsupportedRepositoryOperationException("Not yet implemented.");
+    }
+
     /**
      * @see UserManager#createGroup(String)
      */
-    public Group createGroup(String groupID)
-    		throws AuthorizableExistsException, RepositoryException {
+    public Group createGroup(String groupID) throws AuthorizableExistsException, RepositoryException {
     	return createGroup(groupID, new PrincipalImpl(groupID), null);
     }
     
@@ -705,6 +745,43 @@ public class UserManagerImpl extends ProtectedItemModifier
         setProperty(node, P_PRINCIPAL_NAME, getValue(principal.getName()), true);
     }
 
+    /**
+     * Generate a password value from the specified string and set the
+     * {@link UserConstants#P_PASSWORD} property to the given user node.
+     *
+     * @param userNode A user node.
+     * @param password The password value.
+     * @param forceHash If <code>true</code> the specified password string will
+     * always be hashed; otherwise the hash will only be generated if it appears
+     * to be a {@link PasswordUtility#isPlainTextPassword(String) plain text} password.
+     * @throws RepositoryException If an exception occurs.
+     */
+    void setPassword(NodeImpl userNode, String password, boolean forceHash) throws RepositoryException {
+        if (password == null) {
+            if (userNode.isNew()) {
+                // allow creation of system-only users with 'null' passwords that cannot login
+                return;
+            } else {
+                throw new IllegalArgumentException("Password may not be null.");
+            }
+        }
+        String pwHash;
+        if (forceHash || PasswordUtility.isPlainTextPassword(password)) {
+            try {
+                String algorithm = config.getConfigValue(PARAM_PASSWORD_HASH_ALGORITHM, PasswordUtility.DEFAULT_ALGORITHM);
+                int iterations = config.getConfigValue(PARAM_PASSWORD_HASH_ITERATIONS, PasswordUtility.DEFAULT_ITERATIONS);
+                pwHash = PasswordUtility.buildPasswordHash(password, algorithm, PasswordUtility.DEFAULT_SALT_SIZE, iterations);
+            } catch (NoSuchAlgorithmException e) {
+                throw new RepositoryException(e);
+            } catch (UnsupportedEncodingException e) {
+                throw new RepositoryException(e);
+            }
+        } else {
+            pwHash = password;
+        }
+        setProperty(userNode, P_PASSWORD, getValue(pwHash), userNode.isNew());
+    }
+
     void setProtectedProperty(NodeImpl node, Name propName, Value value) throws RepositoryException, LockException, ConstraintViolationException, ItemExistsException, VersionException {
         setProperty(node, propName, value);
         if (isAutoSave()) {
@@ -831,6 +908,7 @@ public class UserManagerImpl extends ProtectedItemModifier
         try {
             n = session.getNodeById(nodeId);
         } catch (ItemNotFoundException e) {
+            boolean compatibleJR16 = config.getConfigValue(PARAM_COMPATIBLE_JR16, false);
             if (compatibleJR16) {
                 // backwards-compatibility with JR < 2.0 user/group structure that doesn't
                 // allow to determine existence of an authorizable from the id directly.
@@ -927,7 +1005,7 @@ public class UserManagerImpl extends ProtectedItemModifier
      * Create the administrator user. If the node to be created collides
      * with an existing node (ItemExistsException) the existing node gets removed
      * and the admin user node is (re)created.
-     * <p/>
+     * <p>
      * Collision with an existing node may occur under the following circumstances:
      *
      * <ul>
@@ -1018,27 +1096,6 @@ public class UserManagerImpl extends ProtectedItemModifier
         }
     }
 
-    private static int parseMembershipSplitSize(Object param) {
-        int n = 0;
-        if (param != null) {
-            try {
-                n = Integer.parseInt(param.toString());
-                if (n < 4) {
-                    n = 0;
-                }
-            }
-            catch (NumberFormatException e) {
-                n = 0;
-            }
-            if (n == 0) {
-                log.warn("Invalid value {} for {}. Expected integer >= 4",
-                        param.toString(), PARAM_GROUP_MEMBERSHIP_SPLIT_SIZE);
-            }
-        }
-
-        return n;
-    }
-
     //--------------------------------------------------------------------------
     /**
      * Let the configured <code>AuthorizableAction</code>s perform additional
@@ -1049,8 +1106,8 @@ public class UserManagerImpl extends ProtectedItemModifier
      * @param pw The password.
      * @throws RepositoryException If an exception occurs.
      */
-    private void onCreate(User user, String pw) throws RepositoryException {
-        for (AuthorizableAction action : authorizableActions) {
+    void onCreate(User user, String pw) throws RepositoryException {
+        for (AuthorizableAction action : config.getAuthorizableActions()) {
             action.onCreate(user, pw, session);
         }
     }
@@ -1063,8 +1120,8 @@ public class UserManagerImpl extends ProtectedItemModifier
      * @param group The new group.
      * @throws RepositoryException If an exception occurs.
      */
-    private void onCreate(Group group) throws RepositoryException {
-        for (AuthorizableAction action : authorizableActions) {
+    void onCreate(Group group) throws RepositoryException {
+        for (AuthorizableAction action : config.getAuthorizableActions()) {
             action.onCreate(group, session);
         }
     }
@@ -1078,7 +1135,7 @@ public class UserManagerImpl extends ProtectedItemModifier
      * @throws RepositoryException If an exception occurs.
      */
     void onRemove(Authorizable authorizable) throws RepositoryException {
-        for (AuthorizableAction action : authorizableActions) {
+        for (AuthorizableAction action : config.getAuthorizableActions()) {
             action.onRemove(authorizable, session);
         }
     }
@@ -1093,7 +1150,7 @@ public class UserManagerImpl extends ProtectedItemModifier
      * @throws RepositoryException If an exception occurs.
      */
     void onPasswordChange(User user, String password) throws RepositoryException {
-        for (AuthorizableAction action : authorizableActions) {
+        for (AuthorizableAction action : config.getAuthorizableActions()) {
             action.onPasswordChange(user, password, session);
         }
     }
@@ -1331,36 +1388,22 @@ public class UserManagerImpl extends ProtectedItemModifier
         // all child nodes.
         private final long autoExpandSize;
 
-        private NodeCreator(Properties config) {
+        private NodeCreator(UserManagerConfig config) {
             int d = DEFAULT_DEPTH;
             boolean expand = false;
             long size = DEFAULT_SIZE;
 
             if (config != null) {
-                if (config.containsKey(PARAM_DEFAULT_DEPTH)) {
-                    try {
-                        d = Integer.parseInt(config.get(PARAM_DEFAULT_DEPTH).toString());
-                        if (d <= 0) {
-                           log.warn("Invalid defaultDepth '" + d + "' -> using default.");
-                           d = DEFAULT_DEPTH;
-                        }
-                    } catch (NumberFormatException e) {
-                        log.warn("Unable to parse defaultDepth config parameter -> using default.", e);
-                    }
-                }
-                if (config.containsKey(PARAM_AUTO_EXPAND_TREE)) {
-                    expand = Boolean.parseBoolean(config.get(PARAM_AUTO_EXPAND_TREE).toString());
+                d = config.getConfigValue(PARAM_DEFAULT_DEPTH, DEFAULT_DEPTH);
+                if (d <= 0) {
+                    log.warn("Invalid defaultDepth '" + d + "' -> using default.");
+                    d = DEFAULT_DEPTH;
                 }
-                if (config.containsKey(PARAM_AUTO_EXPAND_SIZE)) {
-                    try {
-                        size = Integer.parseInt(config.get(PARAM_AUTO_EXPAND_SIZE).toString());
-                        if (expand && size <= 0) {
-                            log.warn("Invalid autoExpandSize '" + size + "' -> using default.");
-                            size = DEFAULT_SIZE;
-                        }
-                    } catch (NumberFormatException e) {
-                        log.warn("Unable to parse autoExpandSize config parameter -> using default.", e);
-                    }
+                expand = config.getConfigValue(PARAM_AUTO_EXPAND_TREE, false);
+                size = config.getConfigValue(PARAM_AUTO_EXPAND_SIZE, DEFAULT_SIZE);
+                if (expand && size <= 0) {
+                    log.warn("Invalid autoExpandSize '" + size + "' -> using default.");
+                    size = DEFAULT_SIZE;
                 }
             }
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserPerWorkspaceUserManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserPerWorkspaceUserManager.java
index 2f64d06..000967b 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserPerWorkspaceUserManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/UserPerWorkspaceUserManager.java
@@ -29,7 +29,7 @@ import java.util.Properties;
 /**
  * Derived UserManager implementation that allows to switch between autosaving
  * and transient change mode.
- * <p/>
+ * <p>
  * NOTE: This requires that the Session passed to the user manager upon creation
  * is identical to the Session passed to
  * {@link org.apache.jackrabbit.core.security.JackrabbitSecurityManager#getUserManager(Session)}.
@@ -117,6 +117,7 @@ public class UserPerWorkspaceUserManager extends UserManagerImpl {
      * @return The path of the node.
      * @throws RepositoryException If an error occurs while retrieving the path.
      */
+    @Override
     String getPath(Node authorizableNode) throws RepositoryException {
         return authorizableNode.getPath();
     }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/XPathQueryEvaluator.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/XPathQueryEvaluator.java
index 1caf8f6..3f6a02c 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/XPathQueryEvaluator.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/XPathQueryEvaluator.java
@@ -17,6 +17,14 @@
 
 package org.apache.jackrabbit.core.security.user;
 
+import java.util.Iterator;
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.Group;
 import org.apache.jackrabbit.api.security.user.QueryBuilder.Direction;
@@ -34,14 +42,6 @@ import org.apache.jackrabbit.util.Text;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.jcr.Node;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-import javax.jcr.query.Query;
-import javax.jcr.query.QueryManager;
-import java.util.Iterator;
-
 /**
  * This evaluator for {@link org.apache.jackrabbit.api.security.user.Query}s use XPath
  * and some minimal client side filtering.
@@ -131,13 +131,13 @@ public class XPathQueryEvaluator implements XPathQueryBuilder.ConditionVisitor {
         String repPrincipal = session.getJCRName(UserConstants.P_PRINCIPAL_NAME);
 
         xPath.append('(')
-                .append("jcr:like(")
-                .append(repPrincipal)
+                .append("jcr:like(@")
+                .append(escapeForQuery(repPrincipal))
                 .append(",'")
-                .append(condition.getPattern())
+                .append(escapeForQuery(condition.getPattern()))
                 .append("')")
                 .append(" or ")
-                .append("jcr:like(fn:name(.),'")
+                .append("jcr:like(fn:name(),'")
                 .append(escape(condition.getPattern()))
                 .append("')")
                 .append(')');
@@ -146,15 +146,15 @@ public class XPathQueryEvaluator implements XPathQueryBuilder.ConditionVisitor {
     public void visit(XPathQueryBuilder.PropertyCondition condition) throws RepositoryException {
         RelationOp relOp = condition.getOp();
         if (relOp == RelationOp.EX) {
-            xPath.append(condition.getRelPath());
+            xPath.append(escapeForQuery(condition.getRelPath()));
         } else if (relOp == RelationOp.LIKE) {
             xPath.append("jcr:like(")
-                    .append(condition.getRelPath())
+                    .append(escapeForQuery(condition.getRelPath()))
                     .append(",'")
-                    .append(condition.getPattern())
+                    .append(escapeForQuery(condition.getPattern()))
                     .append("')");
         } else {
-            xPath.append(condition.getRelPath())
+            xPath.append(escapeForQuery(condition.getRelPath()))
                     .append(condition.getOp().getOp())
                     .append(format(condition.getValue()));
         }
@@ -162,15 +162,15 @@ public class XPathQueryEvaluator implements XPathQueryBuilder.ConditionVisitor {
 
     public void visit(XPathQueryBuilder.ContainsCondition condition) {
         xPath.append("jcr:contains(")
-                .append(condition.getRelPath())
+                .append(escapeForQuery(condition.getRelPath()))
                 .append(",'")
-                .append(condition.getSearchExpr())
+                .append(escapeForQuery(condition.getSearchExpr()))
                 .append("')");
     }
 
     public void visit(XPathQueryBuilder.ImpersonationCondition condition) {
         xPath.append("@rep:impersonators='")
-                .append(condition.getName())
+                .append(escapeForQuery(condition.getName()))
                 .append('\'');
     }
 
@@ -236,6 +236,21 @@ public class XPathQueryEvaluator implements XPathQueryBuilder.ConditionVisitor {
         return result.toString();
     }
 
+    public static String escapeForQuery(String value) {
+        StringBuilder ret = new StringBuilder();
+        for (int i = 0; i < value.length(); i++) {
+            char c = value.charAt(i);
+            if (c == '\\') {
+                ret.append("\\\\");
+            } else if (c == '\'') {
+                ret.append("''");
+            } else {
+                ret.append(c);
+            }
+        }
+        return ret.toString();
+    }
+
     private String getNtName(Class<? extends Authorizable> selector) throws RepositoryException {
         if (User.class.isAssignableFrom(selector)) {
             return session.getJCRName(UserConstants.NT_REP_USER);
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/AuthorizableAction.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/AuthorizableAction.java
index ff61f1c..417073d 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/AuthorizableAction.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/AuthorizableAction.java
@@ -24,7 +24,20 @@ import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 
 /**
- * <code>AuthorizableAction</code>...
+ * The <code>AuthorizableAction</code> interface provide an implementation
+ * specific way to execute additional validation or write tasks upon
+ *
+ * <ul>
+ * <li>{@link #onCreate(org.apache.jackrabbit.api.security.user.User, String, javax.jcr.Session) User creation},</li>
+ * <li>{@link #onCreate(org.apache.jackrabbit.api.security.user.Group, javax.jcr.Session) Group creation},</li>
+ * <li>{@link #onRemove(org.apache.jackrabbit.api.security.user.Authorizable, javax.jcr.Session) Authorizable removal} and</li>
+ * <li>{@link #onPasswordChange(org.apache.jackrabbit.api.security.user.User, String, javax.jcr.Session) User password modification}.</li>
+ * </ul>
+ *
+ * The actions are attached to a given UserManager instance upon creation
+ * by calling {@link org.apache.jackrabbit.core.security.user.UserManagerImpl#setAuthorizableActions(AuthorizableAction[])}.
+ *
+ * @see org.apache.jackrabbit.core.config.UserManagerConfig
  */
 public interface AuthorizableAction {
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/PasswordValidationAction.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/PasswordValidationAction.java
index 067f0e9..819e613 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/PasswordValidationAction.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/user/action/PasswordValidationAction.java
@@ -17,6 +17,7 @@
 package org.apache.jackrabbit.core.security.user.action;
 
 import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.core.security.user.PasswordUtility;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -69,12 +70,12 @@ public class PasswordValidationAction extends AbstractAuthorizableAction {
     //-------------------------------------------------< AuthorizableAction >---
     @Override
     public void onCreate(User user, String password, Session session) throws RepositoryException {
-        validatePassword(password);
+        validatePassword(password, false); // don't force validation of hashed passwords.
     }
 
     @Override
     public void onPasswordChange(User user, String newPassword, Session session) throws RepositoryException {
-        validatePassword(newPassword);
+        validatePassword(newPassword, true); // force validation of all passwords
     }
 
     //---------------------------------------------------------< BeanConfig >---
@@ -87,7 +88,7 @@ public class PasswordValidationAction extends AbstractAuthorizableAction {
         try {
             pattern = Pattern.compile(constraint);
         } catch (PatternSyntaxException e) {
-            log.warn("Invalid password constraint: ", e.getMessage());
+            log.warn("Invalid password constraint: {}", e.getMessage());
         }
     }
 
@@ -96,15 +97,16 @@ public class PasswordValidationAction extends AbstractAuthorizableAction {
      * Validate the specified password.
      *
      * @param password The password to be validated
+     * @param forceMatch If true the specified password is always validated;
+     * otherwise only if it is a plain text password.
      * @throws RepositoryException If the specified password is too short or
      * doesn't match the specified password pattern.
      */
-    private void validatePassword(String password) throws RepositoryException {
-        if (password != null) {
+    private void validatePassword(String password, boolean forceMatch) throws RepositoryException {
+        if (password != null && (forceMatch || PasswordUtility.isPlainTextPassword(password))) {
             if (pattern != null && !pattern.matcher(password).matches()) {
                 throw new ConstraintViolationException("Password violates password constraint (" + pattern.pattern() + ").");
             }
         }
     }
-
-}
\ No newline at end of file
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/NodeNameNormalizer.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/NodeNameNormalizer.java
new file mode 100644
index 0000000..a6c7a47
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/NodeNameNormalizer.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.session;
+
+import java.text.Normalizer;
+import java.text.Normalizer.Form;
+
+import org.apache.jackrabbit.spi.Name;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Convenience class for checking/logging "weird" node names.
+ * <p>
+ * For now, it only checks that a node name being created uses NFC
+ * 
+ * @see http://www.unicode.org/reports/tr15/tr15-23.html
+ */
+public class NodeNameNormalizer {
+
+    private static Logger log = LoggerFactory.getLogger(NodeNameNormalizer.class);
+
+    public static void check(Name name) {
+        if (log.isDebugEnabled()) {
+            String lname = name.getLocalName();
+            String normalized = Normalizer.normalize(lname, Form.NFC);
+            if (!lname.equals(normalized)) {
+                String message = "The new node name '" + dump(lname) + "' is not in Unicode NFC form ('" + dump(normalized) + "').";
+                log.debug(message, new Exception("Call chain"));
+            }
+        }
+    }
+
+    private static String dump(String lname) {
+        StringBuilder sb = new StringBuilder();
+        for (char c : lname.toCharArray()) {
+            if (c > ' ' && c < 127) {
+                sb.append(c);
+            } else {
+                sb.append(String.format("\\u%04x", (int) c));
+            }
+        }
+        return sb.toString();
+    }
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionContext.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionContext.java
index eb069ae..11f2798 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionContext.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionContext.java
@@ -88,7 +88,7 @@ public class SessionContext implements NamePathResolver {
     /**
      * Privilege manager of this session.
      */
-    private final PrivilegeManager privilegeManager;
+    private final PrivilegeManagerImpl privilegeManager;
 
     /**
      * The namespace registry exposed for this session context that includes
@@ -246,7 +246,7 @@ public class SessionContext implements NamePathResolver {
      *
      * @return the privilege manager.
      */
-    public PrivilegeManager getPrivilegeManager() {
+    public PrivilegeManagerImpl getPrivilegeManager() {
         return privilegeManager;
     }
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionSaveOperation.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionSaveOperation.java
index 7eafd94..c7ebf1e 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionSaveOperation.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionSaveOperation.java
@@ -61,7 +61,9 @@ public class SessionSaveOperation implements SessionWriteOperation<Object> {
                 LOG.debug("Saving changes under " + path);
             }
         }
-        context.getItemManager().getItem(id).save();
+        if (id != null) {
+            context.getItemManager().getItem(id).save();
+        }
         return this;
     }
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionState.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionState.java
index c76a4dd..d296368 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionState.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/session/SessionState.java
@@ -27,7 +27,7 @@ import javax.jcr.Session;
 
 import org.apache.jackrabbit.core.WorkspaceManager;
 import org.apache.jackrabbit.core.observation.ObservationDispatcher;
-import org.apache.jackrabbit.core.stats.RepositoryStatisticsImpl;
+import org.apache.jackrabbit.stats.RepositoryStatisticsImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -226,7 +226,7 @@ public class SessionState {
                         readDuration.addAndGet(time);
                     }
 
-                    log.debug("Performed {} in {}us", operation, time);
+                    log.debug("Performed {} in {}ns", operation, time);
                 }
             } finally {
                 isWriteOperation = wasWriteOperation;
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ChangeLog.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ChangeLog.java
index 49c30d2..f4c8107 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ChangeLog.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ChangeLog.java
@@ -53,6 +53,8 @@ public class ChangeLog {
     @SuppressWarnings("unchecked")
     private final Map<NodeId, NodeReferences> modifiedRefs = (Map<NodeId, NodeReferences>) new LinkedMap();
 
+    private long updateSize;
+
     /**
      * Checks whether this change log contains any changes. This method is
      * used to avoid extra work on updates that contain no changes.
@@ -173,6 +175,17 @@ public class ChangeLog {
     }
 
     /**
+     * Return a flag indicating whether a given item state is marked as
+     * added in this log.
+     *
+     * @return <code>true</code> if item state is marked as added in this
+     *         log; <code>false</code> otherwise
+     */
+    public boolean isAdded(ItemId id) {
+        return addedStates.containsKey(id);
+    }
+
+    /**
      * Returns a flag indicating whether a given item state is marked as
      * modified in this log.
      *
@@ -369,6 +382,24 @@ public class ChangeLog {
     }
 
     /**
+     * Returns the update size of the change log.
+     * 
+     * @return The update size.
+     */
+    public long getUpdateSize() {
+        return updateSize;
+    }
+
+    /**
+     * Sets the update size of the change log.
+     * 
+     * @param updateSize The update size.
+     */
+    public void setUpdateSize(long updateSize) {
+        this.updateSize = updateSize;
+    }
+
+    /**
      * Returns a string representation of this change log for diagnostic
      * purposes.
      *
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ChildNodeEntries.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ChildNodeEntries.java
index bae49b3..0eeaddf 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ChildNodeEntries.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ChildNodeEntries.java
@@ -241,7 +241,7 @@ class ChildNodeEntries implements Cloneable {
     /**
      * Returns a list of <code>ChildNodeEntry</code>s who do only exist in
      * <code>this</code> but not in <code>other</code>.
-     * <p/>
+     * <p>
      * Note that two entries are considered identical in this context if
      * they have the same name and uuid, i.e. the index is disregarded
      * whereas <code>ChildNodeEntry.equals(Object)</code> also compares
@@ -277,7 +277,7 @@ class ChildNodeEntries implements Cloneable {
     /**
      * Returns a list of <code>ChildNodeEntry</code>s who do exist in
      * <code>this</code> <i>and</i> in <code>other</code>.
-     * <p/>
+     * <p>
      * Note that two entries are considered identical in this context if
      * they have the same name and uuid, i.e. the index is disregarded
      * whereas <code>ChildNodeEntry.equals(Object)</code> also compares
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ChildNodeEntry.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ChildNodeEntry.java
index a3471f7..bf30f42 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ChildNodeEntry.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ChildNodeEntry.java
@@ -22,7 +22,7 @@ import org.apache.jackrabbit.core.id.NodeId;
 /**
  * <code>ChildNodeEntry</code> specifies the name, index (in the case of
  * same-name siblings) and the UUID of a child node entry.
- * <p/>
+ * <p>
  * <code>ChildNodeEntry</code> instances are immutable.
  */
 public final class ChildNodeEntry {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/DefaultISMLocking.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/DefaultISMLocking.java
index 94cd9c9..20da2cd 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/DefaultISMLocking.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/DefaultISMLocking.java
@@ -16,8 +16,8 @@
  */
 package org.apache.jackrabbit.core.state;
 
-import static org.apache.jackrabbit.core.TransactionContext.getCurrentThreadId;
-import static org.apache.jackrabbit.core.TransactionContext.isSameThreadId;
+import static org.apache.jackrabbit.data.core.TransactionContext.getCurrentThreadId;
+import static org.apache.jackrabbit.data.core.TransactionContext.isSameThreadId;
 
 import org.apache.jackrabbit.core.id.ItemId;
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/DummyUpdateEventChannel.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/DummyUpdateEventChannel.java
index ac70ed7..fc7634f 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/DummyUpdateEventChannel.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/DummyUpdateEventChannel.java
@@ -24,7 +24,7 @@ import org.apache.jackrabbit.core.cluster.UpdateEventListener;
  * Package-private utility class used as a sentinel by the
  * {@link SharedItemStateManager} class.
  */
-class DummyUpdateEventChannel implements UpdateEventChannel {
+public class DummyUpdateEventChannel implements UpdateEventChannel {
 
     public void updatePrepared(Update update) {}
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/FineGrainedISMLocking.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/FineGrainedISMLocking.java
index ab13b6c..1cf1ab0 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/FineGrainedISMLocking.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/FineGrainedISMLocking.java
@@ -16,8 +16,8 @@
  */
 package org.apache.jackrabbit.core.state;
 
-import static org.apache.jackrabbit.core.TransactionContext.getCurrentThreadId;
-import static org.apache.jackrabbit.core.TransactionContext.isSameThreadId;
+import static org.apache.jackrabbit.data.core.TransactionContext.getCurrentThreadId;
+import static org.apache.jackrabbit.data.core.TransactionContext.isSameThreadId;
 
 import java.util.Collections;
 import java.util.HashMap;
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ISMLocking.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ISMLocking.java
index 2e90131..824d929 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ISMLocking.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ISMLocking.java
@@ -21,7 +21,7 @@ import org.apache.jackrabbit.core.id.ItemId;
 /**
  * <code>ISMLocking</code> defines an interface for a locking strategy of an
  * {@link ItemStateManager}.
- * <p/>
+ * <p>
  * An implementation of <code>ISMLocking</code> must meet the following
  * requirements:
  * <ul>
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java
index cec8bab..00f3844 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java
@@ -172,10 +172,11 @@ public class ItemStateReferenceCache implements ItemStateCache {
         ItemId id = state.getId();
         Map<ItemId, ItemState> segment = getSegment(id);
         synchronized (segment) {
-            if (segment.containsKey(id)) {
+            ItemState s = segment.put(id, state);
+            // overwriting the same instance is OK
+            if (s != null && s != state) {
                 log.warn("overwriting cached entry " + id);
             }
-            segment.put(id, state);
         }
     }
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/LocalItemStateManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/LocalItemStateManager.java
index e1d6f16..613c5db 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/LocalItemStateManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/LocalItemStateManager.java
@@ -444,7 +444,7 @@ public class LocalItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Notification handler gets called for both local states that this state manager
      * has created, as well as states that were created by the shared state manager
      * we're listening to.
@@ -489,7 +489,7 @@ public class LocalItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Notification handler gets called for both local states that this state manager
      * has created, as well as states that were created by the shared state manager
      * we're listening to.
@@ -520,7 +520,7 @@ public class LocalItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Notification handler gets called for both local states that this state manager
      * has created, as well as states that were created by the shared state manager
      * we're listening to.
@@ -545,7 +545,7 @@ public class LocalItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Notification handler gets called for both local states that this state manager
      * has created, as well as states that were created by the shared state manager
      * we're listening to.
@@ -570,7 +570,7 @@ public class LocalItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Optimization: shared state manager we're listening to does not deliver node state changes, therefore the state
      * concerned must be a local state.
      */
@@ -580,7 +580,7 @@ public class LocalItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Optimization: shared state manager we're listening to does not deliver node state changes, therefore the state
      * concerned must be a local state.
      */
@@ -590,7 +590,7 @@ public class LocalItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Optimization: shared state manager we're listening to does not deliver node state changes, therefore the state
      * concerned must be a local state.
      */
@@ -600,7 +600,7 @@ public class LocalItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Optimization: shared state manager we're listening to does not deliver node state changes, therefore the state
      * concerned must be a local state.
      */
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java
index 9cfb08f..90bf9d2 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java
@@ -31,7 +31,7 @@ import org.slf4j.LoggerFactory;
  * cache uses a rough estimate of the memory consumption of the cached item
  * states for calculating the maximum number of entries. The oldest entries
  * are flushed once the cache size has exceeded a certain limit.
- * <p/>
+ * <p>
  * TODO rename class to something more appropriate, e.g. FIFOItemSateCache since
  * it doesn't use a LRU eviction policy anymore.
  */
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NameSet.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NameSet.java
index caf9152..9bbfb89 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NameSet.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NameSet.java
@@ -141,7 +141,7 @@ final class NameSet implements Set<Name>, Cloneable {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * The returned iterator will throw a {@link UnsupportedOperationException}
      * on {@link Iterator#remove()}.
      */
@@ -272,7 +272,7 @@ final class NameSet implements Set<Name>, Cloneable {
 
     /**
      * Implements a simple <code>HashSet<Name></code> cache.
-     * <p/>
+     * <p>
      * Please note that this cache does not ensures that the sets are immutable!
      * It is the responsibility of the caller to make sure that sets passed to
      * {@link #get} are not modified by multiple threads. Modifying a cached
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeReferences.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeReferences.java
index e5e880e..5ac5677 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeReferences.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeReferences.java
@@ -43,7 +43,7 @@ public class NodeReferences implements Serializable {
     /**
      * list of PropertyId's (i.e. the id's of the properties that refer to
      * the target node denoted by <code>id.getTargetId()</code>).
-     * <p/>
+     * <p>
      * note that the list can contain duplicate entries because a specific
      * REFERENCE property can contain multiple references (if it's multi-valued)
      * to potentially the same target node.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java
index 3938828..5e75e75 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java
@@ -16,10 +16,6 @@
  */
 package org.apache.jackrabbit.core.state;
 
-import org.apache.jackrabbit.core.id.ItemId;
-import org.apache.jackrabbit.core.id.NodeId;
-import org.apache.jackrabbit.spi.Name;
-
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
@@ -27,6 +23,10 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 
+import org.apache.jackrabbit.core.id.ItemId;
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.spi.Name;
+
 /**
  * <code>NodeState</code> represents the state of a <code>Node</code>.
  */
@@ -372,8 +372,12 @@ public class NodeState extends ItemState {
             }
         }
         if (oldEntry != null) {
-            notifyNodeAdded(newEntry);
-            notifyNodeRemoved(oldEntry);
+            if (oldEntry.getName().equals(newName)) {
+                notifyNodesReplaced();
+            } else {
+                notifyNodeAdded(newEntry);
+                notifyNodeRemoved(oldEntry);
+            }
             return true;
         }
         return false;
@@ -712,12 +716,12 @@ public class NodeState extends ItemState {
     /**
      * Returns a list of child node entries that exist both in <i>this</i> node
      * state and in the overlayed node state but have been reordered.
-     * <p/>
+     * <p>
      * The list may include only the minimal set of nodes that have been
      * reordered. That is, even though a certain number of nodes have changed
      * their absolute position the list may include less that this number of
      * nodes.
-     * <p/>
+     * <p>
      * Example:<br/>
      * Initial state:
      * <pre>
@@ -849,7 +853,7 @@ public class NodeState extends ItemState {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * If the listener passed is at the same time a <code>NodeStateListener</code>
      * we remember it as well.
      */
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateListener.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateListener.java
index 39b24b5..114d9c4 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateListener.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateListener.java
@@ -39,7 +39,7 @@ public interface NodeStateListener extends ItemStateListener {
     /**
      * Called when a node has been modified, typically as a result of removal
      * or addition of a child node.
-     * <p/>
+     * <p>
      * Please note, that this method is not called if
      * {@link #stateModified(ItemState)} was called.
      *
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateMerger.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateMerger.java
index d539ec7..c989a72 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateMerger.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeStateMerger.java
@@ -33,7 +33,7 @@ import java.util.Set;
 /**
  * Internal utility class used for merging concurrent changes that occurred
  * on different <code>NodeState</code> instances representing the same node.
- * <p/>
+ * <p>
  * See http://issues.apache.org/jira/browse/JCR-584.
  */
 class NodeStateMerger {
@@ -42,8 +42,9 @@ class NodeStateMerger {
      * Tries to silently merge the given <code>state</code> with its
      * externally (e.g. through another session) modified overlayed state
      * in order to avoid an <code>InvalidItemStateException</code>.
-     * <p/>
+     * <p>
      * See http://issues.apache.org/jira/browse/JCR-584.
+     * See also http://issues.apache.org/jira/browse/JCR-3290.
      *
      * @param state node state whose modified overlayed state should be
      *        merged
@@ -109,8 +110,8 @@ class NodeStateMerger {
                     ArrayList<ChildNodeEntry> removed = new ArrayList<ChildNodeEntry>();
 
                     for (ChildNodeEntry cne : state.getAddedChildNodeEntries()) {
-                        // locally added?
-                        if (context.isAdded(cne.getId()) || context.isModified(cne.getId())) {
+                        // locally added or moved?
+                        if (context.isAdded(cne.getId()) || (context.isModified(cne.getId()) && isParent(state, cne, context))) {
                             // a new child node entry has been added to this state;
                             // check for name collisions with other state
                             if (overlayedState.hasChildNodeEntry(cne.getName())) {
@@ -339,6 +340,14 @@ class NodeStateMerger {
         return false;
     }
 
+    private static boolean isParent(NodeState state, ChildNodeEntry entry, MergeContext context) {
+        try {
+            return state.getId().equals(context.getNodeState(entry.getId()).getParentId());
+        } catch (ItemStateException e) {
+            return false;
+        }
+    }
+
     private static boolean isAutoCreated(ChildNodeEntry cne, EffectiveNodeType ent) {
         for (QNodeDefinition def : ent.getAutoCreateNodeDefs()) {
             if (def.getName().equals(cne.getName())) {
@@ -368,5 +377,6 @@ class NodeStateMerger {
         boolean isModified(ItemId id);
         boolean allowsSameNameSiblings(NodeId id);
         EffectiveNodeType getEffectiveNodeType(Name ntName) throws NoSuchNodeTypeException;
+        NodeState getNodeState(NodeId id) throws ItemStateException;
     }
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java
index 9b0f89e..fbc7af3 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SessionItemStateManager.java
@@ -772,7 +772,7 @@ public class SessionItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Notification handler gets called for both transient states that this state manager
      * has created, as well as states that were created by the local state manager
      * we're listening to.
@@ -808,7 +808,7 @@ public class SessionItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Notification handler gets called for both transient states that this state manager
      * has created, as well as states that were created by the local state manager
      * we're listening to.
@@ -821,7 +821,7 @@ public class SessionItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Notification handler gets called for both transient states that this state manager
      * has created, as well as states that were created by the local state manager
      * we're listening to.
@@ -847,7 +847,7 @@ public class SessionItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Notification handler gets called for both transient states that this state manager
      * has created, as well as states that were created by the local state manager
      * we're listening to.
@@ -867,7 +867,7 @@ public class SessionItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Pass notification to listeners if a transient state was modified
      * or if the local state is not overlayed.
      */
@@ -880,7 +880,7 @@ public class SessionItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Pass notification to listeners if a transient state was modified
      * or if the local state is not overlayed.
      */
@@ -893,7 +893,7 @@ public class SessionItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Pass notification to listeners if a transient state was modified
      * or if the local state is not overlayed.
      */
@@ -906,7 +906,7 @@ public class SessionItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Pass notification to listeners if a transient state was modified
      * or if the local state is not overlayed.
      */
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java
index 8fe33e9..593a539 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java
@@ -18,6 +18,7 @@ package org.apache.jackrabbit.core.state;
 
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
@@ -27,6 +28,7 @@ import javax.jcr.RepositoryException;
 import javax.jcr.nodetype.NoSuchNodeTypeException;
 
 import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.cluster.ClusterException;
 import org.apache.jackrabbit.core.cluster.UpdateEventChannel;
 import org.apache.jackrabbit.core.id.ItemId;
 import org.apache.jackrabbit.core.id.NodeId;
@@ -52,7 +54,7 @@ import org.slf4j.LoggerFactory;
  * Shared <code>ItemStateManager</code> (SISM). Caches objects returned from a
  * <code>PersistenceManager</code>. Objects returned by this item state
  * manager are shared among all sessions.
- * <p/>
+ * <p>
  * A shared item state manager operates on a <code>PersistenceManager</code>
  * (PM) that is used to load and store the item states. Additionally, a SISM can
  * have <code>VirtualItemStateProvider</code>s (VISP) that are used to provide
@@ -62,7 +64,7 @@ import org.slf4j.LoggerFactory;
  * SISM during initialization of a workspace. i.e. they are 'mounted' to all
  * workspaces. we assume, that VISP cannot be added dynamically, neither during
  * runtime nor by configuration.
- * <p/>
+ * <p>
  * The states from the VISP are read-only. by the exception for node references.
  * remember that the referrers are stored in a {@link NodeReferences} state,
  * having the ID of the target state.
@@ -87,7 +89,7 @@ import org.slf4j.LoggerFactory;
  *      states of different VISP).
  *      those do currently not occur and are therefore not supported.
  * </ol>
- * <p/>
+ * <p>
  * if VISP are not dynamic, there is not risk that NV-type references can dangle
  * (since a VISP cannot be 'unmounted', leaving eventual references dangling).
  * although multi-workspace-referrers are not explicitly supported, the
@@ -393,7 +395,7 @@ public class SharedItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Notifications are received for items that this manager created itself or items that are
      * managed by one of the virtual providers.
      */
@@ -407,7 +409,7 @@ public class SharedItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Notifications are received for items that this manager created itself or items that are
      * managed by one of the virtual providers.
      */
@@ -417,7 +419,7 @@ public class SharedItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Notifications are received for items that this manager created itself or items that are
      * managed by one of the virtual providers.
      */
@@ -431,7 +433,7 @@ public class SharedItemStateManager
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Notifications are received for items that this manager created itself or items that are
      * managed by one of the virtual providers.
      */
@@ -493,6 +495,11 @@ public class SharedItemStateManager
     class Update implements org.apache.jackrabbit.core.cluster.Update {
 
         /**
+         * Attribute name used to store the size of the update.
+         */
+        private static final String ATTRIBUTE_UPDATE_SIZE = "updateSize";
+
+        /**
          * Local change log.
          */
         private final ChangeLog local;
@@ -562,7 +569,11 @@ public class SharedItemStateManager
             virtualNodeReferences = new ChangeLog[virtualProviders.length];
 
             // let listener know about change
-            eventChannel.updateCreated(this);
+            try {
+                eventChannel.updateCreated(this);
+            } catch (ClusterException e) {
+                throw new ItemStateException(e.getMessage(), e);
+            }
 
             try {
                 writeLock = acquireWriteLock(local);
@@ -650,7 +661,7 @@ public class SharedItemStateManager
                                             return ntReg.getEffectiveNodeType(ntName);
                                         }
 
-                                        protected NodeState getNodeState(NodeId id)
+                                        public NodeState getNodeState(NodeId id)
                                                 throws ItemStateException {
                                             if (local.has(id)) {
                                                 return (NodeState) local.get(id);
@@ -675,14 +686,21 @@ public class SharedItemStateManager
 
                     shared.modified(state.getOverlayedState());
                 }
-                for (ItemState state : local.deletedStates()) {
-                    state.connect(getItemState(state.getId()));
-                    if (state.isStale()) {
-                        String msg = state.getId() + " has been modified externally";
-                        log.debug(msg);
-                        throw new StaleItemStateException(msg);
+                Iterator<ItemState> deleted = local.deletedStates().iterator();
+                while (deleted.hasNext()) {
+                    ItemState state = deleted.next();
+                    try {
+                        state.connect(getItemState(state.getId()));
+                        if (state.isStale()) {
+                            String msg = state.getId() + " has been modified externally";
+                            log.debug(msg);
+                            throw new StaleItemStateException(msg);
+                        }
+                        shared.deleted(state.getOverlayedState());
+                    } catch (NoSuchItemStateException e) {
+                        // item state was already deleted externally
+                        deleted.remove();
                     }
-                    shared.deleted(state.getOverlayedState());
                 }
                 for (ItemState state : local.addedStates()) {
                     if (state.isNode() && state.getStatus() != ItemState.STATUS_NEW) {
@@ -729,10 +747,14 @@ public class SharedItemStateManager
                 events.createEventStates(rootNodeId, local, SharedItemStateManager.this);
 
                 // let listener know about change
-                eventChannel.updatePrepared(this);
+                try {
+                    eventChannel.updatePrepared(this);
+                } catch (ClusterException e) {
+                    throw new ItemStateException(e.getMessage(), e);
+                }
 
                 if (VALIDATE_HIERARCHY) {
-                    log.info("Validating change-set hierarchy");
+                    log.debug("Validating change-set hierarchy");
                     try {
                         validateHierarchy(local);
                     } catch (ItemStateException e) {
@@ -768,6 +790,7 @@ public class SharedItemStateManager
                 /* Store items in the underlying persistence manager */
                 long t0 = System.currentTimeMillis();
                 persistMgr.store(shared);
+                setAttribute(ATTRIBUTE_UPDATE_SIZE, shared.getUpdateSize());
                 succeeded = true;
                 if (log.isDebugEnabled()) {
                     long t1 = System.currentTimeMillis();
@@ -781,6 +804,24 @@ public class SharedItemStateManager
 
             ISMLocking.ReadLock readLock = null;
             try {
+                // make sure new item states are present/referenced in cache
+                // we do this before the lock is downgraded to a read lock
+                // because then other threads will be able to read from
+                // this SISM again and potentially read an added item state
+                // before the ones here are put into the cache (via
+                // shared.persisted()). See JCR-3345
+                for (ItemState state : shared.addedStates()) {
+                    // there is one exception though. it is possible that the
+                    // shared ChangeLog contains the an item both as removed and
+                    // added. For those items we don't update the cache here,
+                    // because that would lead to WARN messages in the
+                    // ItemStateReferenceCache. See JCR-3419
+                    if (!shared.deleted(state.getId())) {
+                        state.setStatus(ItemState.STATUS_EXISTING);
+                        cache.cache(state);
+                    }
+                }
+
                 // downgrade to read lock
                 readLock = writeLock.downgrade();
                 writeLock = null;
@@ -797,24 +838,32 @@ public class SharedItemStateManager
                     }
                 }
 
-                /* dispatch the events */
-                events.dispatch();
             } finally {
                 // Let listener know about finished operation. This needs
                 // to happen in the finally block so that the cluster lock
                 // always gets released, even if a post-store() exception
                 // is thrown from the code above. See also JCR-2272.
                 String path = events.getSession().getUserID()
-                        + "@" + events.getCommonPath();
+                        + "@" + events.getSession().getWorkspace().getName()
+                        + ":" + events.getCommonPath();
                 eventChannel.updateCommitted(this, path);
+                setAttribute(ATTRIBUTE_UPDATE_SIZE, null);
 
                 if (writeLock != null) {
                     // exception occurred before downgrading lock
                     writeLock.release();
                     writeLock = null;
                 } else if (readLock != null) {
-                    readLock.release();
+                    try {
+                        if (succeeded) {
+                            /* dispatch the events */
+                            events.dispatch();
+                        }
+                    } finally {
+                        readLock.release();
+                    }
                 }
+
             }
         }
 
@@ -831,14 +880,14 @@ public class SharedItemStateManager
 
                 for (ItemState state : shared.modifiedStates()) {
                     try {
-                        state.copy(loadItemState(state.getId()), false);
+                        state.copy(loadItemState(state.getId()), true);
                     } catch (ItemStateException e) {
                         state.discard();
                     }
                 }
                 for (ItemState state : shared.deletedStates()) {
                     try {
-                        state.copy(loadItemState(state.getId()), false);
+                        state.copy(loadItemState(state.getId()), true);
                     } catch (ItemStateException e) {
                         state.discard();
                     }
@@ -1301,7 +1350,7 @@ public class SharedItemStateManager
                 NodeId oldParentId = overlayedState.getParentId();
 
                 // The parent should not be deleted
-                if (parentId != null && changeLog.deleted(parentId)) {
+                if (parentId != null && changeLog.deleted(parentId) && !changeLog.isAdded(parentId)) {
                     String message = "Parent of node with id " + id + " has been deleted";
                     log.error(message);
                     throw new ItemStateException(message);
@@ -1312,7 +1361,7 @@ public class SharedItemStateManager
                 }
 
                 if (!(parentId == null && oldParentId == null)
-                        && !parentId.equals(oldParentId)) {
+                        && (parentId != null && !parentId.equals(oldParentId))) {
                     // This node (not the root) has been moved; check
                     // whether the parent has been modified as well
                     if (changeLog.has(parentId)) {
@@ -1397,7 +1446,7 @@ public class SharedItemStateManager
             if (sharedSet.contains(expectedParent)) {
                 return;
             }
-            String message = "Child node has another parent id " + parentId + ", expected " + expectedParent;
+            String message = "Child node " + childState.getId() + " has another parent id " + parentId + ", expected " + expectedParent;
             log.error(message);
             throw new ItemStateException(message);
         }
@@ -1502,18 +1551,18 @@ public class SharedItemStateManager
         boolean holdingWriteLock = false;
 
         ISMLocking.WriteLock wLock = null;
-        try {
-            wLock = acquireWriteLock(external);
-            holdingWriteLock = true;
-
-            doExternalUpdate(external);
-        } catch (ItemStateException e) {
-            String msg = "Unable to acquire write lock.";
-            log.error(msg);
-        }
-
         ISMLocking.ReadLock rLock = null;
         try {
+	        try {
+	            wLock = acquireWriteLock(external);
+	            holdingWriteLock = true;
+	
+	            doExternalUpdate(external);
+	        } catch (ItemStateException e) {
+	            String msg = "Unable to acquire write lock.";
+	            log.error(msg);
+	        }
+
             if (wLock != null) {
                 rLock = wLock.downgrade();
                 holdingWriteLock = false;
@@ -1679,7 +1728,7 @@ public class SharedItemStateManager
     /**
      * Makes sure child node entry for mandatory jcr:activities exist.
      * Repositories upgraded from 1.x do not have it.
-     * <p/>
+     * <p>
      * This method assumes that the jcr:system node already exists.
      *
      * @throws ItemStateException if an error occurs while reading or writing to
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/XAItemStateManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/XAItemStateManager.java
index c186d74..30ac2c6 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/XAItemStateManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/XAItemStateManager.java
@@ -26,15 +26,15 @@ import javax.jcr.ReferentialIntegrityException;
 
 import org.apache.commons.collections.Predicate;
 import org.apache.commons.collections.iterators.FilterIterator;
-import org.apache.jackrabbit.core.InternalXAResource;
-import org.apache.jackrabbit.core.TransactionContext;
-import org.apache.jackrabbit.core.TransactionException;
 import org.apache.jackrabbit.core.id.ItemId;
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.id.PropertyId;
 import org.apache.jackrabbit.core.observation.EventStateCollectionFactory;
 import org.apache.jackrabbit.core.value.InternalValue;
 import org.apache.jackrabbit.core.virtual.VirtualItemStateProvider;
+import org.apache.jackrabbit.data.core.InternalXAResource;
+import org.apache.jackrabbit.data.core.TransactionContext;
+import org.apache.jackrabbit.data.core.TransactionException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -89,7 +89,7 @@ public class XAItemStateManager extends LocalItemStateManager implements Interna
      * @param factory        event state collection factory
      * @param attributeName  the attribute name, if {@code null} then a default name is used
      */
-    private XAItemStateManager(SharedItemStateManager sharedStateMgr,
+    protected XAItemStateManager(SharedItemStateManager sharedStateMgr,
                               EventStateCollectionFactory factory,
                               String attributeName,
                               ItemStateCacheFactory cacheFactory) {
@@ -230,7 +230,7 @@ public class XAItemStateManager extends LocalItemStateManager implements Interna
     //-----------------------------------------------------< ItemStateManager >
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * If this state manager is committing changes, this method first checks
      * the commitLog ThreadLocal. Else if associated to a transaction check
      * the transactional change log. Fallback is always the call to the base
@@ -262,7 +262,7 @@ public class XAItemStateManager extends LocalItemStateManager implements Interna
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * If this state manager is committing changes, this method first checks
      * the commitLog ThreadLocal. Else if associated to a transaction check
      * the transactional change log. Fallback is always the call to the base
@@ -304,7 +304,7 @@ public class XAItemStateManager extends LocalItemStateManager implements Interna
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * If this state manager is committing changes, this method first
      * checks the commitLog ThreadLocal. Else if associated to a transaction
      * check the transactional change log. Fallback is always the call to
@@ -321,7 +321,7 @@ public class XAItemStateManager extends LocalItemStateManager implements Interna
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * If this state manager is committing changes, this method first
      * checks the commitLog ThreadLocal. Else if associated to a transaction
      * check the transactional change log. Fallback is always the call to
@@ -340,7 +340,7 @@ public class XAItemStateManager extends LocalItemStateManager implements Interna
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * If associated with a transaction, simply merge the changes given to
      * the ones already known (removing items that were first added and
      * then again deleted).
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/QueryStatCore.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/QueryStatCore.java
deleted file mode 100644
index 33bae00..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/QueryStatCore.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.stats;
-
-import org.apache.jackrabbit.api.stats.QueryStat;
-
-/**
- * Extends external facing {@link QueryStat} with some internal operations
- * 
- */
-public interface QueryStatCore extends QueryStat {
-
-    /**
-     * Logs the call of each query ran on the repository.
-     * 
-     * @param language
-     *            the query language, see
-     *            {@link org.apache.jackrabbit.spi.commons.name.NameConstants#JCR_LANGUAGE}
-     * @param statement
-     *            the query
-     * @param duration
-     *            time in ms
-     */
-    void logQuery(final String language, final String statement, long durationMs);
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/QueryStatDtoComparator.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/QueryStatDtoComparator.java
deleted file mode 100644
index 31a4742..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/QueryStatDtoComparator.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.stats;
-
-import java.util.Comparator;
-
-import org.apache.jackrabbit.api.stats.QueryStatDto;
-
-/**
- * QueryStatDto comparator by duration
- * 
- */
-public class QueryStatDtoComparator implements Comparator<QueryStatDto> {
-    public int compare(QueryStatDto o1, QueryStatDto o2) {
-        return new Long(o1.getDuration()).compareTo(o2.getDuration());
-    }
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/QueryStatDtoImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/QueryStatDtoImpl.java
deleted file mode 100644
index b1ef693..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/QueryStatDtoImpl.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.stats;
-
-import java.util.Calendar;
-import java.util.Date;
-
-import org.apache.jackrabbit.api.stats.QueryStatDto;
-
-/**
- * Object that holds statistical info about a query.
- * 
- */
-public class QueryStatDtoImpl implements QueryStatDto {
-
-    private static final long serialVersionUID = 1L;
-
-    /**
-     * lazy, computed at call time
-     */
-    private long position;
-
-    /**
-     * the time that the query was created
-     */
-    private final Date creationTime;
-
-    /**
-     * run duration in ms
-     */
-    private final long durationMs;
-
-    /**
-     * query language
-     */
-    private final String language;
-
-    /**
-     * query statement
-     */
-    private final String statement;
-
-    /**
-     * used in popular queries list
-     */
-    private int occurrenceCount = 1;
-
-    public QueryStatDtoImpl(final String language, final String statement,
-            long durationMs) {
-        this.durationMs = durationMs;
-        this.language = language;
-        this.statement = statement;
-
-        Calendar c = Calendar.getInstance();
-        c.setTimeInMillis(System.currentTimeMillis() - durationMs);
-        this.creationTime = c.getTime();
-    }
-
-    public long getDuration() {
-        return durationMs;
-    }
-
-    public String getLanguage() {
-        return language;
-    }
-
-    public String getStatement() {
-        return statement;
-    }
-
-    public String getCreationTime() {
-        return creationTime.toString();
-    }
-
-    public long getPosition() {
-        return position;
-    }
-
-    public void setPosition(long position) {
-        this.position = position;
-    }
-
-    @Override
-    public String toString() {
-        return "QueryStat [creationTime=" + creationTime + ", duration="
-                + durationMs + ", language=" + language + ", statement="
-                + statement + "]";
-    }
-
-    public int getOccurrenceCount() {
-        return occurrenceCount;
-    }
-
-    public void setOccurrenceCount(int occurrenceCount) {
-        this.occurrenceCount = occurrenceCount;
-    }
-
-    @Override
-    public int hashCode() {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result
-                + ((language == null) ? 0 : language.hashCode());
-        result = prime * result
-                + ((statement == null) ? 0 : statement.hashCode());
-        return result;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj)
-            return true;
-        if (obj == null)
-            return false;
-        if (getClass() != obj.getClass())
-            return false;
-        QueryStatDtoImpl other = (QueryStatDtoImpl) obj;
-        if (language == null) {
-            if (other.language != null)
-                return false;
-        } else if (!language.equals(other.language))
-            return false;
-        if (statement == null) {
-            if (other.statement != null)
-                return false;
-        } else if (!statement.equals(other.statement))
-            return false;
-        return true;
-    }
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/QueryStatDtoOccurrenceComparator.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/QueryStatDtoOccurrenceComparator.java
deleted file mode 100644
index 29c5be2..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/QueryStatDtoOccurrenceComparator.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.stats;
-
-import java.util.Comparator;
-
-/**
- * QueryStatDto comparator by occurrence count
- * 
- * used by the popular queries queue
- * 
- */
-public class QueryStatDtoOccurrenceComparator implements
-        Comparator<QueryStatDtoImpl> {
-    public int compare(QueryStatDtoImpl o1, QueryStatDtoImpl o2) {
-        return new Integer(o1.getOccurrenceCount()).compareTo(o2
-                .getOccurrenceCount());
-    }
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/QueryStatImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/QueryStatImpl.java
deleted file mode 100644
index 86ecc78..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/QueryStatImpl.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.stats;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.concurrent.PriorityBlockingQueue;
-
-import org.apache.jackrabbit.api.stats.QueryStatDto;
-
-/**
- * Default {@link QueryStatCore} implementation
- * 
- */
-public class QueryStatImpl implements QueryStatCore {
-
-    private final static Comparator<QueryStatDto> comparator = new QueryStatDtoComparator();
-
-    private final BoundedPriorityBlockingQueue<QueryStatDto> slowQueries = new BoundedPriorityBlockingQueue<QueryStatDto>(
-            15, comparator);
-
-    private final static Comparator<QueryStatDtoImpl> comparatorOccurrence = new QueryStatDtoOccurrenceComparator();
-
-    /**
-     * the real queue size will be bigger than the desired number of popular
-     * queries by POPULAR_QUEUE_MULTIPLIER times
-     */
-    private static final int POPULAR_QUEUE_MULTIPLIER = 5;
-
-    private final BoundedPriorityBlockingQueue<QueryStatDtoImpl> popularQueries = new BoundedPriorityBlockingQueue<QueryStatDtoImpl>(
-            15 * POPULAR_QUEUE_MULTIPLIER, comparatorOccurrence);
-
-    private static final class BoundedPriorityBlockingQueue<E> extends
-            PriorityBlockingQueue<E> {
-
-        private static final long serialVersionUID = 1L;
-        private int maxSize;
-
-        public BoundedPriorityBlockingQueue(int maxSize,
-                Comparator<? super E> comparator) {
-            super(maxSize + 1, comparator);
-            this.maxSize = maxSize;
-        }
-
-        @Override
-        public boolean offer(E e) {
-            boolean s = super.offer(e);
-            if (!s) {
-                return false;
-            }
-            if (size() > maxSize) {
-                poll();
-            }
-            return true;
-        }
-
-        public synchronized void setMaxSize(int maxSize) {
-            if (maxSize < this.maxSize) {
-                // shrink the queue
-                int delta = this.maxSize - maxSize;
-                for (int i = 0; i < delta; i++) {
-                    poll();
-                }
-            }
-            this.maxSize = maxSize;
-        }
-
-        public int getMaxSize() {
-            return maxSize;
-        }
-    }
-
-    private boolean enabled = false;
-
-    public QueryStatImpl() {
-    }
-
-    public int getSlowQueriesQueueSize() {
-        return slowQueries.getMaxSize();
-    }
-
-    public void setSlowQueriesQueueSize(int size) {
-        slowQueries.setMaxSize(size);
-    }
-
-    public boolean isEnabled() {
-        return enabled;
-    }
-
-    public synchronized void setEnabled(boolean enabled) {
-        this.enabled = enabled;
-    }
-
-    public void logQuery(final String language, final String statement,
-            long durationMs) {
-        if (!enabled) {
-            return;
-        }
-        final QueryStatDtoImpl qs = new QueryStatDtoImpl(language, statement,
-                durationMs);
-        slowQueries.offer(qs);
-        Iterator<QueryStatDtoImpl> iterator = popularQueries.iterator();
-        while (iterator.hasNext()) {
-            QueryStatDtoImpl qsdi = iterator.next();
-            if (qsdi.equals(qs)) {
-                qs.setOccurrenceCount(qsdi.getOccurrenceCount() + 1);
-                iterator.remove();
-                break;
-            }
-        }
-        popularQueries.offer(qs);
-    }
-
-    public void clearSlowQueriesQueue() {
-        slowQueries.clear();
-    }
-
-    public QueryStatDto[] getSlowQueries() {
-        QueryStatDto[] top = slowQueries.toArray(new QueryStatDto[slowQueries
-                .size()]);
-        Arrays.sort(top, Collections.reverseOrder(comparator));
-        for (int i = 0; i < top.length; i++) {
-            top[i].setPosition(i + 1);
-        }
-        return top;
-    }
-
-    public QueryStatDto[] getPopularQueries() {
-        QueryStatDtoImpl[] top = popularQueries
-                .toArray(new QueryStatDtoImpl[popularQueries.size()]);
-        Arrays.sort(top, Collections.reverseOrder(comparatorOccurrence));
-        int retSize = Math.min(popularQueries.size(),
-                popularQueries.getMaxSize() / POPULAR_QUEUE_MULTIPLIER);
-        QueryStatDto[] retval = new QueryStatDto[retSize];
-        for (int i = 0; i < retSize; i++) {
-            top[i].setPosition(i + 1);
-            retval[i] = top[i];
-        }
-        return top;
-    }
-
-    public int getPopularQueriesQueueSize() {
-        return popularQueries.getMaxSize();
-    }
-
-    public void setPopularQueriesQueueSize(int size) {
-        popularQueries.setMaxSize(size * POPULAR_QUEUE_MULTIPLIER);
-    }
-
-    public void clearPopularQueriesQueue() {
-        popularQueries.clear();
-    }
-
-    public void reset() {
-        clearSlowQueriesQueue();
-        clearPopularQueriesQueue();
-    }
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/RepositoryStatisticsImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/RepositoryStatisticsImpl.java
deleted file mode 100644
index 363b8d3..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/RepositoryStatisticsImpl.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.stats;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.TreeMap;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.apache.jackrabbit.api.stats.RepositoryStatistics;
-import org.apache.jackrabbit.api.stats.RepositoryStatistics.Type;
-import org.apache.jackrabbit.api.stats.TimeSeries;
-
-public class RepositoryStatisticsImpl implements
-        Iterable<Map.Entry<Type, TimeSeries>>, RepositoryStatistics {
-
-    private final Map<Type, TimeSeriesRecorder> recorders =
-            new HashMap<Type, TimeSeriesRecorder>();
-
-    private final Map<Type, TimeSeriesAverage> avg =
-            new HashMap<Type, TimeSeriesAverage>();
-
-    public RepositoryStatisticsImpl() {
-        getOrCreateRecorder(Type.SESSION_COUNT);
-        getOrCreateRecorder(Type.SESSION_LOGIN_COUNTER);
-
-        createAvg(Type.SESSION_READ_COUNTER, Type.SESSION_READ_DURATION,
-                Type.SESSION_READ_AVERAGE);
-        createAvg(Type.SESSION_WRITE_COUNTER, Type.SESSION_WRITE_DURATION,
-                Type.SESSION_WRITE_AVERAGE);
-        createAvg(Type.BUNDLE_CACHE_MISS_COUNTER,
-                Type.BUNDLE_CACHE_MISS_DURATION, Type.BUNDLE_CACHE_MISS_AVERAGE);
-        createAvg(Type.BUNDLE_WRITE_COUNTER, Type.BUNDLE_WRITE_DURATION,
-                Type.BUNDLE_WRITE_AVERAGE);
-        createAvg(Type.QUERY_COUNT, Type.QUERY_DURATION, Type.QUERY_AVERAGE);
-
-    }
-
-    private void createAvg(Type count, Type duration, Type avgTs) {
-        avg.put(avgTs, new TimeSeriesAverage(getOrCreateRecorder(duration),
-                getOrCreateRecorder(count)));
-    }
-
-    public RepositoryStatisticsImpl(ScheduledExecutorService executor) {
-        this();
-        executor.scheduleAtFixedRate(new Runnable() {
-            public void run() {
-                recordOneSecond();
-            }
-        }, 1, 1, TimeUnit.SECONDS);
-    }
-
-    public synchronized Iterator<Entry<Type, TimeSeries>> iterator() {
-        Map<Type, TimeSeries> map = new TreeMap<Type, TimeSeries>();
-        map.putAll(recorders);
-        map.putAll(avg);
-        return map.entrySet().iterator();
-    }
-
-    public AtomicLong getCounter(Type type) {
-        return getOrCreateRecorder(type).getCounter();
-    }
-
-    public TimeSeries getTimeSeries(Type type) {
-        if (avg.containsKey(type)) {
-            return avg.get(type);
-        } else {
-            return getOrCreateRecorder(type);
-        }
-    }
-
-    private synchronized TimeSeriesRecorder getOrCreateRecorder(Type type) {
-        TimeSeriesRecorder recorder = recorders.get(type);
-        if (recorder == null) {
-            recorder = new TimeSeriesRecorder(type);
-            recorders.put(type, recorder);
-        }
-        return recorder;
-    }
-
-    private synchronized void recordOneSecond() {
-        for (TimeSeriesRecorder recorder : recorders.values()) {
-            recorder.recordOneSecond();
-        }
-    }
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/StatManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/StatManager.java
index 8b888cc..d71c017 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/StatManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/StatManager.java
@@ -18,6 +18,8 @@ package org.apache.jackrabbit.core.stats;
 
 import static java.lang.Boolean.getBoolean;
 
+import org.apache.jackrabbit.stats.QueryStatCore;
+import org.apache.jackrabbit.stats.QueryStatImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/TimeSeriesAverage.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/TimeSeriesAverage.java
deleted file mode 100644
index e8f66db..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/TimeSeriesAverage.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.stats;
-
-import org.apache.jackrabbit.api.stats.TimeSeries;
-
-/**
- * Time series of the average calculated by dividing a measured
- * value by the counter of events during the measurement period.
- */
-class TimeSeriesAverage implements TimeSeries {
-
-    /** Value */
-    private final TimeSeries value;
-
-    /** Value */
-    private final TimeSeries counter;
-
-    public TimeSeriesAverage(TimeSeries value, TimeSeries counter) {
-        this.value = value;
-        this.counter = counter;
-    }
-
-    //----------------------------------------------------------< TimeSeries >
-
-    public long[] getValuePerSecond() {
-        long[] values = value.getValuePerSecond();
-        long[] counts = counter.getValuePerSecond();
-        return divide(values, counts);
-    }
-
-    public long[] getValuePerMinute() {
-        long[] values = value.getValuePerMinute();
-        long[] counts = counter.getValuePerMinute();
-        return divide(values, counts);
-    }
-
-    public synchronized long[] getValuePerHour() {
-        long[] values = value.getValuePerHour();
-        long[] counts = counter.getValuePerHour();
-        return divide(values, counts);
-    }
-
-    public synchronized long[] getValuePerWeek() {
-        long[] values = value.getValuePerWeek();
-        long[] counts = counter.getValuePerWeek();
-        return divide(values, counts);
-    }
-
-    //-------------------------------------------------------------< private >
-
-    /**
-     * Per-entry division of two arrays.
-     *
-     * @param a array
-     * @param b array
-     * @return result of division
-     */
-    private long[] divide(long[] a, long[] b) {
-        long[] c = new long[a.length];
-        for (int i = 0; i < a.length; i++) {
-            if (b[i] != 0) {
-                c[i] = a[i] / b[i];
-            } else {
-                c[i] = 0;
-            }
-        }
-        return c;
-    }
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/TimeSeriesRecorder.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/TimeSeriesRecorder.java
deleted file mode 100755
index 5aa3986..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/stats/TimeSeriesRecorder.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.stats;
-
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.apache.jackrabbit.api.stats.TimeSeries;
-import org.apache.jackrabbit.api.stats.RepositoryStatistics.Type;
-
-/**
- * Recorder of a time series. An instance of this class records (and clears)
- * the state of a given {@link AtomicLong} counter once every second and
- * exposes the collected time series through the {@link TimeSeries}
- * interface.
- */
-class TimeSeriesRecorder implements TimeSeries {
-
-    /** Type */
-    private final Type type;
-    
-    /** Value */
-    private final AtomicLong counter = new AtomicLong();
-
-    /** Measured value per second over the last minute. */
-    private final long[] valuePerSecond = new long[60];
-
-    /** Measured value per minute over the last hour. */
-    private final long[] valuePerMinute = new long[60];
-
-    /** Measured value per hour over the last week. */
-    private final long[] valuePerHour = new long[7 * 24];
-
-    /** Measured value per week over the last three years. */
-    private final long[] valuePerWeek = new long[3 * 52];
-
-    /** Current second (index in {@link #valuePerSecond}) */
-    private int seconds = 0;
-
-    /** Current minute (index in {@link #valuePerMinute}) */
-    private int minutes = 0;
-
-    /** Current hour (index in {@link #valuePerHour}) */
-    private int hours = 0;
-
-    /** Current week (index in {@link #valuePerWeek}) */
-    private int weeks = 0;
-
-    public TimeSeriesRecorder(Type type) {
-        this.type = type;
-    }
-
-    /**
-     * Returns the {@link AtomicLong} instance used to measure the value for
-     * the time series.
-     *
-     * @return value
-     */
-    public AtomicLong getCounter() {
-        return counter;
-    }
-
-    /**
-     * Records the number of measured values over the past second and resets
-     * the counter. This method should be scheduled to be called once per
-     * second.
-     */
-    public synchronized void recordOneSecond() {
-        if (type.isResetValueEachSecond()) {
-            valuePerSecond[seconds++] = counter.getAndSet(0);
-        } else {
-            valuePerSecond[seconds++] = counter.get();
-        }
-        if (seconds == valuePerSecond.length) {
-            seconds = 0;
-            valuePerMinute[minutes++] = aggregate(valuePerSecond);
-        }
-        if (minutes == valuePerMinute.length) {
-            minutes = 0;
-            valuePerHour[hours++] = aggregate(valuePerMinute);
-        }
-        if (hours == valuePerHour.length) {
-            hours = 0;
-            valuePerWeek[weeks++] = aggregate(valuePerHour);
-        }
-        if (weeks == valuePerWeek.length) {
-            weeks = 0;
-        }
-    }
-
-    //----------------------------------------------------------< TimeSeries >
-
-    public synchronized long[] getValuePerSecond() {
-        return cyclicCopyFrom(valuePerSecond, seconds);
-    }
-
-    public synchronized long[] getValuePerMinute() {
-        return cyclicCopyFrom(valuePerMinute, minutes);
-    }
-
-    public synchronized long[] getValuePerHour() {
-        return cyclicCopyFrom(valuePerHour, hours);
-    }
-
-    public synchronized long[] getValuePerWeek() {
-        return cyclicCopyFrom(valuePerWeek, weeks);
-    }
-
-    //-------------------------------------------------------------< private >
-
-    /**
-     * Returns the sum of all entries in the given array.
-     *
-     * @param array array to be summed
-     * @return sum of entries
-     */
-    private long aggregate(long[] array) {
-        long sum = 0;
-        for (int i = 0; i < array.length; i++) {
-
-            sum += array[i];
-        }
-        if (type.isResetValueEachSecond()) {
-            return sum;
-        }
-        return sum / array.length;
-    }
-
-    /**
-     * Returns a copy of the given cyclical array, with the element at
-     * the given position as the first element of the returned array.
-     *
-     * @param array cyclical array
-     * @param pos position of the first element
-     * @return copy of the array
-     */
-    private static long[] cyclicCopyFrom(long[] array, int pos) {
-        long[] reverse = new long[array.length];
-        for (int i = 0; i < array.length; i++) {
-            reverse[i] = array[(pos + i) % array.length];
-        }
-        return reverse;
-    }
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/XAReentrantLock.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/XAReentrantLock.java
new file mode 100644
index 0000000..cf0eeb4
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/XAReentrantLock.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.util;
+
+import static org.apache.jackrabbit.data.core.TransactionContext.isSameThreadId;
+
+import org.apache.jackrabbit.data.core.TransactionContext;
+
+import EDU.oswego.cs.dl.util.concurrent.ReentrantLock;
+
+/**
+ * A reentrant lock for synchronization. 
+ * Unlike a normal reentrant lock, this one allows the lock
+ * to be re-entered not just by a thread that's already holding the lock but
+ * by any thread within the same transaction.
+ */
+public class XAReentrantLock extends ReentrantLock {
+	
+	/**
+	 * The active lock holder of this {@link ReentrantLock}
+	 */
+    private Object activeId;
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void acquire() throws InterruptedException {
+        if (Thread.interrupted()) {
+            throw new InterruptedException();
+        }
+        Object currentId = TransactionContext.getCurrentThreadId();
+        synchronized(this) {
+            if (currentId == activeId || (activeId != null && isSameThreadId(activeId, currentId))) { 
+                ++holds_;
+            } else {
+                try {  
+                    while (activeId != null) {
+                        wait(); 
+                    }
+                    activeId = currentId;
+                    holds_ = 1;
+                } catch (InterruptedException ex) {
+                    notify();
+                    throw ex;
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void release()  {
+        Object currentId = TransactionContext.getCurrentThreadId();
+        if (activeId != null && !isSameThreadId(activeId, currentId)) {
+            throw new Error("Illegal Lock usage"); 
+        }
+
+        if (--holds_ == 0) {
+            activeId = null;
+            notify(); 
+        }
+    }
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/XAReentrantWriterPreferenceReadWriteLock.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/XAReentrantWriterPreferenceReadWriteLock.java
new file mode 100644
index 0000000..109a8d8
--- /dev/null
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/XAReentrantWriterPreferenceReadWriteLock.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.util;
+
+import static org.apache.jackrabbit.data.core.TransactionContext.getCurrentThreadId;
+import static org.apache.jackrabbit.data.core.TransactionContext.isSameThreadId;
+import EDU.oswego.cs.dl.util.concurrent.ReentrantWriterPreferenceReadWriteLock;
+
+/**
+ * A reentrant read-write lock for synchronization. 
+ * Unlike a normal reentrant lock, this one allows the lock
+ * to be re-entered not just by a thread that's already holding the lock but
+ * by any thread within the same transaction.
+ */
+public class XAReentrantWriterPreferenceReadWriteLock extends ReentrantWriterPreferenceReadWriteLock{
+	
+	private Object activeWriter;
+    
+    /**
+     * {@inheritDoc}
+     */
+    protected boolean allowReader() {
+        Object currentId = getCurrentThreadId();
+        return (activeWriter == null && waitingWriters_ == 0) || isSameThreadId(activeWriter, currentId);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected synchronized boolean startWrite() {
+    	Object currentId = getCurrentThreadId();
+        if (activeWriter != null && isSameThreadId(activeWriter, currentId)) { // already held; re-acquire
+        	++writeHolds_;
+            return true;
+        } else if (writeHolds_ == 0) {
+        	if (activeReaders_ == 0 || (readers_.size() == 1 && readers_.get(currentId) != null)) {
+        		activeWriter = currentId;
+        		writeHolds_ = 1;
+        		return true;
+        	} else {
+        		return false;
+        	}
+        } else {
+        	return false;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected synchronized Signaller endWrite() {
+        --writeHolds_;
+        if (writeHolds_ > 0) {  // still being held
+        	return null;
+        } else {
+        	activeWriter = null;
+            if (waitingReaders_ > 0 && allowReader()) {
+                return readerLock_;
+            } else if (waitingWriters_ > 0) {
+                return writerLock_;
+            } else {
+                return null;
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+	@SuppressWarnings("unchecked")
+	protected synchronized boolean startRead() {
+		Object currentId = getCurrentThreadId();
+	    Object c = readers_.get(currentId);
+	    if (c != null) { // already held -- just increment hold count
+	    	readers_.put(currentId, new Integer(((Integer)(c)).intValue()+1));
+	    	++activeReaders_;
+	    	return true;
+	    } else if (allowReader()) {
+	    	readers_.put(currentId, IONE);
+	    	++activeReaders_;
+	    	return true;
+	    } else {
+	    	return false;
+	    }
+	}
+	
+    /**
+     * {@inheritDoc}
+     */
+	@SuppressWarnings("unchecked")
+	protected synchronized Signaller endRead() {
+		Object currentId = getCurrentThreadId();
+	    Object c = readers_.get(currentId);
+	    if (c == null) {
+	    	throw new IllegalStateException();
+	    }
+	    --activeReaders_;
+	    if (c != IONE) { // more than one hold; decrement count
+	    	int h = ((Integer)(c)).intValue()-1;
+	    	Integer ih = (h == 1)? IONE : new Integer(h);
+	    	readers_.put(currentId, ih);
+	    	return null;
+	    } else {
+	    	readers_.remove(currentId);
+	    
+	    	if (writeHolds_ > 0) { // a write lock is still held
+	    		return null;
+	    	} else if (activeReaders_ == 0 && waitingWriters_ > 0) {
+	    		return writerLock_;
+	    	} else  {
+	    		return null;
+	    	}
+	    }
+	}
+
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionFactory.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionFactory.java
deleted file mode 100644
index ee7dee6..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionFactory.java
+++ /dev/null
@@ -1,366 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.util.db;
-
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.RepositoryException;
-import javax.naming.Context;
-import javax.naming.NamingException;
-import javax.sql.DataSource;
-
-import org.apache.commons.dbcp.BasicDataSource;
-import org.apache.commons.dbcp.DelegatingConnection;
-import org.apache.jackrabbit.core.config.DataSourceConfig;
-import org.apache.jackrabbit.core.config.DataSourceConfig.DataSourceDefinition;
-import org.apache.jackrabbit.util.Base64;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A factory for new database connections.
- * Supported are regular JDBC drivers, as well as
- * JNDI resources.
- *
- * FIXME: the registry currently is ClassLoader wide. I.e., if you start two repositories
- * then you share the registered datasources...
- */
-public final class ConnectionFactory {
-
-    private static final Logger log = LoggerFactory.getLogger(ConnectionFactory.class);
-
-    /**
-     * The lock to protect the fields of this class.
-     */
-    private final Object lock = new Object();
-
-    /**
-     * The data sources without logical name. The keys in the map are based on driver-url-user combination.
-     */
-    private final Map<String, DataSource> keyToDataSource = new HashMap<String, DataSource>();
-
-    /**
-     * The configured data sources with logical name. The keys in the map are the logical name.
-     */
-    private final Map<String, DataSource> nameToDataSource = new HashMap<String, DataSource>();
-
-    /**
-     * The configured data source defs. The keys in the map are the logical name.
-     */
-    private final Map<String, DataSourceDefinition> nameToDataSourceDef = new HashMap<String, DataSourceDefinition>();
-
-    /**
-     * The list of data sources created by this factory.
-     */
-    private final List<BasicDataSource> created = new ArrayList<BasicDataSource>();
-
-    private boolean closed = false;
-
-    /**
-     * Registers a number of data sources.
-     *
-     * @param dsc the {@link DataSourceConfig} which contains the configuration
-     */
-    public void registerDataSources(DataSourceConfig dsc) throws RepositoryException {
-        synchronized (lock) {
-            sanityCheck();
-            for (DataSourceDefinition def : dsc.getDefinitions()) {
-                Class<?> driverClass = getDriverClass(def.getDriver());
-                if (driverClass != null
-                        && Context.class.isAssignableFrom(driverClass)) {
-                    DataSource ds = getJndiDataSource((Class<Context>) driverClass, def.getUrl());
-                    nameToDataSource.put(def.getLogicalName(), ds);
-                    nameToDataSourceDef.put(def.getLogicalName(), def);
-                } else {
-                    BasicDataSource bds =
-                        getDriverDataSource(driverClass, def.getUrl(), def.getUser(), def.getPassword());
-                    if (def.getMaxPoolSize() > 0) {
-                        bds.setMaxActive(def.getMaxPoolSize());
-                    }
-                    if (def.getValidationQuery() != null && !"".equals(def.getValidationQuery().trim())) {
-                        bds.setValidationQuery(def.getValidationQuery());
-                    }
-                    nameToDataSource.put(def.getLogicalName(), bds);
-                    nameToDataSourceDef.put(def.getLogicalName(), def);
-                }
-            }
-        }
-    }
-
-    /**
-     * Retrieves a configured data source by logical name.
-     *
-     * @param logicalName the name of the {@code DataSource}
-     * @return a {@code DataSource}
-     * @throws RepositoryException if there is no {@code DataSource} with the given name
-     */
-    public DataSource getDataSource(String logicalName) throws RepositoryException {
-        synchronized (lock) {
-            sanityCheck();
-            DataSource ds = nameToDataSource.get(logicalName);
-            if (ds == null) {
-                throw new RepositoryException("DataSource with logicalName " + logicalName
-                        + " has not been configured");
-            }
-            return ds;
-        }
-    }
-
-    /**
-     * @param logicalName the name of the {@code DataSource}
-     * @return the configured database type
-     * @throws RepositoryException if there is no {@code DataSource} with the given name
-     */
-    public String getDataBaseType(String logicalName) throws RepositoryException {
-        synchronized (lock) {
-            sanityCheck();
-            DataSourceDefinition def = nameToDataSourceDef.get(logicalName);
-            if (def == null) {
-                throw new RepositoryException("DataSource with logicalName " + logicalName
-                        + " has not been configured");
-            }
-            return def.getDbType();
-        }
-    }
-
-    /**
-     * Retrieve a {@code DataSource} for the specified properties.
-     * This can be a JNDI Data Source as well. To do that,
-     * the driver class name must reference a {@code javax.naming.Context} class
-     * (for example {@code javax.naming.InitialContext}), and the URL must be the JNDI URL
-     * (for example {@code java:comp/env/jdbc/Test}).
-     *
-     * @param driver the JDBC driver or the Context class
-     * @param url the database URL
-     * @param user the user name
-     * @param password the password
-     * @return the {@code DataSource}
-     * @throws RepositoryException if the driver could not be loaded
-     * @throws SQLException if the connection could not be established
-     */
-    public DataSource getDataSource(String driver, String url, String user, String password)
-            throws RepositoryException, SQLException    {
-        final String key = driver + url + user;
-        synchronized(lock) {
-            sanityCheck();
-            DataSource ds = keyToDataSource.get(key);
-            if (ds == null) {
-                ds = createDataSource(
-                        driver, url, user, Base64.decodeIfEncoded(password));
-                keyToDataSource.put(key, ds);
-            }
-            return ds;
-        }
-    }
-
-    /**
-     *
-     */
-    public void close() {
-        synchronized(lock) {
-            sanityCheck();
-            for (BasicDataSource ds : created) {
-                try {
-                    ds.close();
-                } catch (SQLException e) {
-                    log.error("failed to close " + ds, e);
-                }
-            }
-            keyToDataSource.clear();
-            nameToDataSource.clear();
-            nameToDataSourceDef.clear();
-            created.clear();
-            closed = true;
-        }
-    }
-
-    /**
-     * Needed for pre-10R2 Oracle blob support....:(
-     *
-     * This method actually assumes that we are using commons DBCP 1.2.2.
-     *
-     * @param con the commons-DBCP {@code DelegatingConnection} to unwrap
-     * @return the unwrapped connection
-     */
-    public static Connection unwrap(Connection con) throws SQLException {
-        if (con instanceof DelegatingConnection) {
-            return ((DelegatingConnection)con).getInnermostDelegate();
-        } else {
-            throw new SQLException("failed to unwrap connection of class " + con.getClass().getName() +
-                ", expected it to be a " + DelegatingConnection.class.getName());
-        }
-    }
-
-    private void sanityCheck() {
-        if (closed) {
-            throw new IllegalStateException("this factory has already been closed");
-        }
-    }
-
-    /**
-     * Create a new pooling data source or finds an existing JNDI data source (depends on driver).
-     *
-     * @param driver
-     * @param url
-     * @param user
-     * @param password
-     * @return
-     * @throws RepositoryException
-     */
-    private DataSource createDataSource(String driver, String url, String user, String password)
-            throws RepositoryException {
-        Class<?> driverClass = getDriverClass(driver);
-        if (driverClass != null
-                && Context.class.isAssignableFrom(driverClass)) {
-            @SuppressWarnings("unchecked")
-            DataSource database = getJndiDataSource((Class<Context>) driverClass, url);
-            if (user == null && password == null) {
-                return database;
-            } else {
-                return new DataSourceWrapper(database, user, password);
-            }
-        } else {
-            return getDriverDataSource(driverClass, url, user, password);
-        }
-    }
-
-    /**
-     * Loads and returns the given JDBC driver (or JNDI context) class.
-     * Returns <code>null</code> if a class name is not given.
-     *
-     * @param driver driver class name
-     * @return driver class, or <code>null</code>
-     * @throws RepositoryException if the class can not be loaded
-     */
-    private Class<?> getDriverClass(String driver)
-            throws RepositoryException {
-        try {
-            if (driver != null && driver.length() > 0) {
-                return Class.forName(driver);
-            } else {
-                return null;
-            }
-        } catch (ClassNotFoundException e) {
-            throw new RepositoryException(
-                    "Could not load JDBC driver class " + driver, e);
-        }
-    }
-
-    /**
-     * Returns the JDBC {@link DataSource} bound to the given name in
-     * the JNDI {@link Context} identified by the given class.
-     *
-     * @param contextClass class that is instantiated to get the JNDI context
-     * @param name name of the DataSource within the JNDI context
-     * @return the DataSource bound in JNDI
-     * @throws RepositoryException if the JNDI context can not be accessed,
-     *                             or if the named DataSource is not found
-     */
-    private DataSource getJndiDataSource(
-            Class<Context> contextClass, String name)
-            throws RepositoryException {
-        try {
-            Object object = contextClass.newInstance().lookup(name);
-            if (object instanceof DataSource) {
-                return (DataSource) object;
-            } else {
-                throw new RepositoryException(
-                        "Object " + object + " with JNDI name "
-                        + name + " is not a JDBC DataSource");
-            }
-        } catch (InstantiationException e) {
-            throw new RepositoryException(
-                    "Invalid JNDI context: " + contextClass.getName(), e);
-        } catch (IllegalAccessException e) {
-            throw new RepositoryException(
-                    "Invalid JNDI context: " + contextClass.getName(), e);
-        } catch (NamingException e) {
-            throw new RepositoryException(
-                    "JNDI name not found: " + name, e);
-        }
-    }
-
-    /**
-     * Creates and returns a pooling JDBC {@link DataSource} for accessing
-     * the database identified by the given driver class and JDBC
-     * connection URL. The driver class can be <code>null</code> if
-     * a specific driver has not been configured.
-     *
-     * @param driverClass the JDBC driver class, or <code>null</code>
-     * @param url the JDBC connection URL
-     * @return pooling DataSource for accessing the specified database
-     */
-    private BasicDataSource getDriverDataSource(
-            Class<?> driverClass, String url, String user, String password) {
-        BasicDataSource ds = new BasicDataSource();
-        created.add(ds);
-
-        if (driverClass != null) {
-            try {
-                // Workaround for Apache Derby:
-                // The JDBC specification recommends the Class.forName
-                // method without the .newInstance() method call,
-                // but it is required after a Derby 'shutdown'
-                driverClass.newInstance();
-            } catch (Throwable e) {
-                // Ignore exceptions as there's no requirement for
-                // a JDBC driver class to have a public default constructor
-            }
-
-            ds.setDriverClassName(driverClass.getName());
-        }
-
-        ds.setUrl(url);
-        ds.setUsername(user);
-        ds.setPassword(password);
-        ds.setDefaultAutoCommit(true);
-        ds.setTestOnBorrow(false);
-        ds.setTestWhileIdle(true);
-        ds.setTimeBetweenEvictionRunsMillis(1000);
-        ds.setMaxActive(-1); // unlimited
-        ds.setValidationQuery(guessValidationQuery(url));
-        ds.setAccessToUnderlyingConnectionAllowed(true);
-        ds.setPoolPreparedStatements(true);
-        ds.setMaxOpenPreparedStatements(-1); // unlimited
-        return ds;
-    }
-
-    private String guessValidationQuery(String url) {
-        if (url.contains("derby")) {
-            return "values(1)";
-        } else if (url.contains("mysql")) {
-            return "select 1";
-        } else if (url.contains("sqlserver") || url.contains("jtds")) {
-            return "select 1";
-        } else if (url.contains("oracle")) {
-            return "select 'validationQuery' from dual";
-        } else if (url.contains("postgresql")) {
-            return "select 1";
-        } else if (url.contains("h2")) {
-            return "select 1";
-        } else if (url.contains("db2")) {
-            return "values(1)";
-        }
-        log.warn("Failed to guess validation query for URL " + url);
-        return null;
-    }
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionHelper.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionHelper.java
deleted file mode 100644
index 042410d..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionHelper.java
+++ /dev/null
@@ -1,516 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.util.db;
-
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-
-import javax.sql.DataSource;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * This class provides convenience methods to execute SQL statements. They can be either executed in isolation
- * or within the context of a JDBC transaction; the so-called <i>batch mode</i> (use the {@link #startBatch()}
- * and {@link #endBatch(boolean)} methods for this).
- *
- * <p/>
- *
- * This class contains logic to retry execution of SQL statements. If this helper is <i>not</i> in batch mode
- * and if a statement fails due to an {@code SQLException}, then it is retried. If the {@code block} argument
- * of the constructor call was {@code false} then it is retried only once. Otherwise the statement is retried
- * until either it succeeds or the thread is interrupted. This clearly assumes that the only cause of {@code
- * SQLExceptions} is faulty {@code Connections} which are restored eventually. <br/> <strong>Note</strong>:
- * This retry logic only applies to the following methods:
- * <ul>
- * <li>{@link #exec(String, Object...)}</li>
- * <li>{@link #update(String, Object[])}</li>
- * <li>{@link #exec(String, Object[], boolean, int)}</li>
- * </ul>
- *
- * <p/>
- *
- * This class is not thread-safe and if it is to be used by multiple threads then the clients must make sure
- * that access to this class is properly synchronized.
- *
- * <p/>
- *
- * <strong>Implementation note</strong>: The {@code Connection} that is retrieved from the {@code DataSource}
- * in {@link #getConnection()} may be broken. This is so because if an internal {@code DataSource} is used,
- * then this is a commons-dbcp {@code DataSource} with a <code>testWhileIdle</code> validation strategy (see
- * the {@code ConnectionFactory} class). Furthermore, if it is a {@code DataSource} obtained through JNDI then we
- * can make no assumptions about the validation strategy. This means that our retry logic must either assume that
- * the SQL it tries to execute can do so without errors (i.e., the statement is valid), or it must implement its
- * own validation strategy to apply. Currently, the former is in place.
- */
-public class ConnectionHelper {
-
-    static Logger log = LoggerFactory.getLogger(ConnectionHelper.class);
-
-    private static final int RETRIES = 1;
-
-    private static final int SLEEP_BETWEEN_RETRIES_MS = 100;
-
-    final boolean blockOnConnectionLoss;
-
-    private final boolean checkTablesWithUserName;
-
-    protected final DataSource dataSource;
-
-    private ThreadLocal<Connection> batchConnectionTl = new ThreadLocal<Connection>();
-    
-    /**
-     * The default fetchSize is '0'. This means the fetchSize Hint will be ignored 
-     */
-    private int fetchSize = 0;
-
-    /**
-     * @param dataSrc the {@link DataSource} on which this instance acts
-     * @param block whether the helper should transparently block on DB connection loss (otherwise it retries
-     *            once and if that fails throws exception)
-     */
-    public ConnectionHelper(DataSource dataSrc, boolean block) {
-        dataSource = dataSrc;
-        checkTablesWithUserName = false;
-        blockOnConnectionLoss = block;
-    }
-
-    /**
-     * @param dataSrc the {@link DataSource} on which this instance acts
-     * @param checkWithUserName whether the username is to be used for the {@link #tableExists(String)} method
-     * @param block whether the helper should transparently block on DB connection loss (otherwise it throws exceptions)
-     */
-    protected ConnectionHelper(DataSource dataSrc, boolean checkWithUserName, boolean block) {
-        dataSource = dataSrc;
-        checkTablesWithUserName = checkWithUserName;
-        blockOnConnectionLoss = block;
-    }
-
-    /**
-     * @param dataSrc the {@link DataSource} on which this instance acts
-     * @param checkWithUserName whether the username is to be used for the {@link #tableExists(String)} method
-     * @param block whether the helper should transparently block on DB connection loss (otherwise it throws exceptions)
-     * @param fetchSize the fetchSize that will be used per default
-     */
-    protected ConnectionHelper(DataSource dataSrc, boolean checkWithUserName, boolean block, int fetchSize) {
-        dataSource = dataSrc;
-        checkTablesWithUserName = checkWithUserName;
-        blockOnConnectionLoss = block;
-        this.fetchSize = fetchSize;
-    }
-
-    /**
-     * A utility method that makes sure that <code>identifier</code> does only consist of characters that are
-     * allowed in names on the target database. Illegal characters will be escaped as necessary.
-     *
-     * This method is not affected by the
-     *
-     * @param identifier the identifier to convert to a db specific identifier
-     * @return the db-normalized form of the given identifier
-     * @throws SQLException if an error occurs
-     */
-    public final String prepareDbIdentifier(String identifier) throws SQLException {
-        if (identifier == null) {
-            return null;
-        }
-        String legalChars = "ABCDEFGHIJKLMNOPQRSTUVWXZY0123456789_";
-        legalChars += getExtraNameCharacters();
-        String id = identifier.toUpperCase();
-        StringBuilder escaped = new StringBuilder();
-        for (int i = 0; i < id.length(); i++) {
-            char c = id.charAt(i);
-            if (legalChars.indexOf(c) == -1) {
-                replaceCharacter(escaped, c);
-            } else {
-                escaped.append(c);
-            }
-        }
-        return escaped.toString();
-    }
-
-    /**
-     * Called from {@link #prepareDbIdentifier(String)}. Default implementation replaces the illegal
-     * characters with their hexadecimal encoding.
-     *
-     * @param escaped the escaped db identifier
-     * @param c the character to replace
-     */
-    protected void replaceCharacter(StringBuilder escaped, char c) {
-        escaped.append("_x");
-        String hex = Integer.toHexString(c);
-        escaped.append("0000".toCharArray(), 0, 4 - hex.length());
-        escaped.append(hex);
-        escaped.append("_");
-    }
-
-    /**
-     * Returns true if we are currently in a batch mode, false otherwise.
-     * @return true if the current thread is running in batch mode, false otherwise.
-     */
-    protected boolean inBatchMode()
-    {
-      return batchConnectionTl.get() != null;
-    }
-
-    /**
-     * The default implementation returns the {@code extraNameCharacters} provided by the databases metadata.
-     *
-     * @return the additional characters for identifiers supported by the db
-     * @throws SQLException on error
-     */
-    private String getExtraNameCharacters() throws SQLException {
-        Connection con = dataSource.getConnection();
-        try {
-            DatabaseMetaData metaData = con.getMetaData();
-            return metaData.getExtraNameCharacters();
-        } finally {
-            DbUtility.close(con, null, null);
-        }
-    }
-
-    /**
-     * Checks whether the given table exists in the database.
-     *
-     * @param tableName the name of the table
-     * @return whether the given table exists
-     * @throws SQLException on error
-     */
-    public final boolean tableExists(String tableName) throws SQLException {
-        Connection con = dataSource.getConnection();
-        ResultSet rs = null;
-        boolean schemaExists = false;
-        String name = tableName;
-        try {
-            DatabaseMetaData metaData = con.getMetaData();
-            if (metaData.storesLowerCaseIdentifiers()) {
-                name = tableName.toLowerCase();
-            } else if (metaData.storesUpperCaseIdentifiers()) {
-                name = tableName.toUpperCase();
-            }
-            String userName = null;
-            if (checkTablesWithUserName) {
-                userName = metaData.getUserName();
-            }
-            rs = metaData.getTables(null, userName, name, null);
-            schemaExists = rs.next();
-        } finally {
-            DbUtility.close(con, null, rs);
-        }
-        return schemaExists;
-    }
-
-    /**
-     * Starts the <i>batch mode</i>. If an {@link SQLException} is thrown, then the batch mode is not started. <p/>
-     * <strong>Important:</strong> clients that call this method must make sure that
-     * {@link #endBatch(boolean)} is called eventually.
-     *
-     * @throws SQLException on error
-     */
-    public final void startBatch() throws SQLException {
-        if (inBatchMode()) {
-            throw new SQLException("already in batch mode");
-        }
-        Connection batchConnection = null;
-        try {
-            batchConnection = getConnection();
-            batchConnection.setAutoCommit(false);
-            batchConnectionTl.set(batchConnection);
-        } catch (SQLException e) {
-            // Strive for failure atomicity
-            if (batchConnection != null) {
-                DbUtility.close(batchConnection, null, null);
-            }
-            batchConnectionTl.remove();
-            throw e;
-        }
-    }
-
-    /**
-     * This method always ends the <i>batch mode</i>.
-     *
-     * @param commit whether the changes in the batch should be committed or rolled back
-     * @throws SQLException if the commit or rollback of the underlying JDBC Connection threw an {@code
-     *             SQLException}
-     */
-    public final void endBatch(boolean commit) throws SQLException {
-        if (!inBatchMode()) {
-            throw new IllegalStateException("not in batch mode");
-        }
-        try {
-            if (commit) {
-                batchConnectionTl.get().commit();
-            } else {
-                batchConnectionTl.get().rollback();
-            }
-        } finally {
-            DbUtility.close(batchConnectionTl.get(), null, null);
-            batchConnectionTl.set(null);
-        }
-    }
-
-    /**
-     * Executes a general SQL statement and immediately closes all resources.
-     *
-     * Note: We use a Statement if there are no parameters to avoid a problem on
-     * the Oracle 10g JDBC driver w.r.t. :NEW and :OLD keywords that triggers ORA-17041.
-     *
-     * @param sql an SQL statement string
-     * @param params the parameters for the SQL statement
-     * @throws SQLException on error
-     */
-    public final void exec(final String sql, final Object... params) throws SQLException {
-        new RetryManager<Void>() {
-
-            @Override
-            protected Void call() throws SQLException {
-                reallyExec(sql, params);
-                return null;
-            }
-
-        }.doTry();
-    }
-
-    void reallyExec(String sql, Object... params) throws SQLException {
-        Connection con = null;
-        Statement stmt = null;
-        try {
-            con = getConnection();
-            if (params == null || params.length == 0) {
-                stmt = con.createStatement();
-                stmt.execute(sql);
-            } else {
-                PreparedStatement p = con.prepareStatement(sql);
-                stmt = p;
-                execute(p, params);
-            }
-        } finally {
-            closeResources(con, stmt, null);
-        }
-    }
-
-    /**
-     * Executes an update or delete statement and returns the update count.
-     *
-     * @param sql an SQL statement string
-     * @param params the parameters for the SQL statement
-     * @return the update count
-     * @throws SQLException on error
-     */
-    public final int update(final String sql, final Object... params) throws SQLException {
-        return new RetryManager<Integer>() {
-
-            @Override
-            protected Integer call() throws SQLException {
-                return reallyUpdate(sql, params);
-            }
-
-        }.doTry();
-    }
-
-    int reallyUpdate(String sql, Object... params) throws SQLException {
-        Connection con = null;
-        PreparedStatement stmt = null;
-        try {
-            con = getConnection();
-            stmt = con.prepareStatement(sql);
-            return execute(stmt, params).getUpdateCount();
-        } finally {
-            closeResources(con, stmt, null);
-        }
-    }
-
-    /**
-     * Executes a SQL query and returns the {@link ResultSet}. The
-     * returned {@link ResultSet} should be closed by clients.
-     *
-     * @param sql an SQL statement string
-     * @param params the parameters for the SQL statement
-     * @return a {@link ResultSet}
-     */
-    public final ResultSet query(String sql, Object... params) throws SQLException {
-        return exec(sql, params, false, 0);
-    }
-
-    /**
-     * Executes a general SQL statement and returns the {@link ResultSet} of the executed statement. The
-     * returned {@link ResultSet} should be closed by clients.
-     *
-     * @param sql an SQL statement string
-     * @param params the parameters for the SQL statement
-     * @param returnGeneratedKeys whether generated keys should be returned
-     * @param maxRows the maximum number of rows in a potential {@link ResultSet} (0 means no limit)
-     * @return a {@link ResultSet}
-     * @throws SQLException on error
-     */
-    public final ResultSet exec(final String sql, final Object[] params, final boolean returnGeneratedKeys,
-            final int maxRows) throws SQLException {
-        return new RetryManager<ResultSet>() {
-
-            @Override
-            protected ResultSet call() throws SQLException {
-                return reallyExec(sql, params, returnGeneratedKeys, maxRows);
-            }
-
-        }.doTry();
-    }
-
-    ResultSet reallyExec(String sql, Object[] params, boolean returnGeneratedKeys, int maxRows)
-            throws SQLException {
-        Connection con = null;
-        PreparedStatement stmt = null;
-        ResultSet rs = null;
-        try {
-            con = getConnection();
-            if (returnGeneratedKeys) {
-                stmt = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
-            } else {
-                stmt = con.prepareStatement(sql);
-            }
-            stmt.setMaxRows(maxRows);
-            int currentFetchSize = this.fetchSize;
-            if (0 < maxRows && maxRows < currentFetchSize) {
-            	currentFetchSize = maxRows; // JCR-3090
-            }
-            stmt.setFetchSize(currentFetchSize);
-            execute(stmt, params);
-            if (returnGeneratedKeys) {
-                rs = stmt.getGeneratedKeys();
-            } else {
-                rs = stmt.getResultSet();
-            }
-            // Don't wrap null
-            if (rs == null) {
-                return null;
-            }
-            if (inBatchMode()) {
-                return ResultSetWrapper.newInstance(null, stmt, rs);
-            } else {
-                return ResultSetWrapper.newInstance(con, stmt, rs);
-            }
-        } catch (SQLException e) {
-            closeResources(con, stmt, rs);
-            throw e;
-        }
-    }
-
-	/**
-     * Gets a connection based on the {@code batchMode} state of this helper. The connection should be closed
-     * by a call to {@link #closeResources(Connection, Statement, ResultSet)} which also takes the {@code
-     * batchMode} state into account.
-     *
-     * @return a {@code Connection} to use, based on the batch mode state
-     * @throws SQLException on error
-     */
-    protected final Connection getConnection() throws SQLException {
-        if (inBatchMode()) {
-            return batchConnectionTl.get();
-        } else {
-            Connection con = dataSource.getConnection();
-            // JCR-1013: Setter may fail unnecessarily on a managed connection
-            if (!con.getAutoCommit()) {
-                con.setAutoCommit(true);
-            }
-            return con;
-        }
-    }
-
-    /**
-     * Closes the given resources given the {@code batchMode} state.
-     *
-     * @param con the {@code Connection} obtained through the {@link #getConnection()} method
-     * @param stmt a {@code Statement}
-     * @param rs a {@code ResultSet}
-     */
-    protected final void closeResources(Connection con, Statement stmt, ResultSet rs) {
-        if (inBatchMode()) {
-            DbUtility.close(null, stmt, rs);
-        } else {
-            DbUtility.close(con, stmt, rs);
-        }
-    }
-
-    /**
-     * This method is used by all methods of this class that execute SQL statements. This default
-     * implementation sets all parameters and unwraps {@link StreamWrapper} instances. Subclasses may override
-     * this method to do something special with the parameters. E.g., the {@link Oracle10R1ConnectionHelper}
-     * overrides it in order to add special blob handling.
-     *
-     * @param stmt the {@link PreparedStatement} to execute
-     * @param params the parameters
-     * @return the executed statement
-     * @throws SQLException on error
-     */
-    protected PreparedStatement execute(PreparedStatement stmt, Object[] params) throws SQLException {
-        for (int i = 0; params != null && i < params.length; i++) {
-            Object p = params[i];
-            // FIXME: what about already consumed input streams when in a retry?
-            if (p instanceof StreamWrapper) {
-                StreamWrapper wrapper = (StreamWrapper) p;
-                stmt.setBinaryStream(i + 1, wrapper.getStream(), (int) wrapper.getSize());
-            } else {
-                stmt.setObject(i + 1, p);
-            }
-        }
-        stmt.execute();
-        return stmt;
-    }
-
-    /**
-     * This class encapsulates the logic to retry a method invocation if it threw an SQLException.
-     *
-     * @param <T> the return type of the method which is retried if it failed
-     */
-    public abstract class RetryManager<T> {
-
-        public final T doTry() throws SQLException {
-            if (inBatchMode()) {
-                return call();
-            } else {
-                boolean sleepInterrupted = false;
-                int failures = 0;
-                SQLException lastException = null;
-                while (!sleepInterrupted && (blockOnConnectionLoss || failures <= RETRIES)) {
-                    try {
-                        return call();
-                    } catch (SQLException e) {
-                        lastException = e;
-                    }
-                    log.error("Failed to execute SQL (stacktrace on DEBUG log level)", lastException);
-                    log.debug("Failed to execute SQL", lastException);
-                    failures++;
-                    if (blockOnConnectionLoss || failures <= RETRIES) { // if we're going to try again
-                        try {
-                            Thread.sleep(SLEEP_BETWEEN_RETRIES_MS);
-                        } catch (InterruptedException e1) {
-                            Thread.currentThread().interrupt();
-                            sleepInterrupted = true;
-                            log.error("Interrupted: canceling retry");
-                        }
-                    }
-                }
-                throw lastException;
-            }
-        }
-
-        protected abstract T call() throws SQLException;
-    }
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/DbUtility.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/DbUtility.java
deleted file mode 100644
index e3c73eb..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/DbUtility.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.util.db;
-
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * This class contains some database utility methods.
- */
-public final class DbUtility {
-
-    private static final Logger LOG = LoggerFactory.getLogger(DbUtility.class);
-
-    /**
-     * Private constructor for utility class pattern.
-     */
-    private DbUtility() {
-    }
-
-    /**
-     * This is a utility method which closes the given resources without throwing exceptions. Any exceptions
-     * encountered are logged instead.
-     * 
-     * @param rs the {@link ResultSet} to close, may be null
-     */
-    public static void close(ResultSet rs) {
-        close(null, null, rs);
-    }
-
-    /**
-     * This is a utility method which closes the given resources without throwing exceptions. Any exceptions
-     * encountered are logged instead.
-     * 
-     * @param con the {@link Connection} to close, may be null
-     * @param stmt the {@link Statement} to close, may be null
-     * @param rs the {@link ResultSet} to close, may be null
-     */
-    public static void close(Connection con, Statement stmt, ResultSet rs) {
-        try {
-            if (rs != null) {
-                rs.close();
-            }
-        } catch (SQLException e) {
-            logException("failed to close ResultSet", e);
-        } finally {
-            try {
-                if (stmt != null) {
-                    stmt.close();
-                }
-            } catch (SQLException e) {
-                logException("failed to close Statement", e);
-            } finally {
-                try {
-                    if (con != null) {
-                        con.close();
-                    }
-                } catch (SQLException e) {
-                    logException("failed to close Connection", e);
-                }
-            }
-        }
-    }
-
-    /**
-     * Logs an SQL exception on error level, and debug level (more detail).
-     * 
-     * @param message the message
-     * @param se the exception
-     */
-    public static void logException(String message, SQLException e) {
-        if (message != null) {
-            LOG.error(message);
-        }
-        LOG.error("       Reason: " + e.getMessage());
-        LOG.error("   State/Code: " + e.getSQLState() + "/" + e.getErrorCode());
-        LOG.debug("   dump:", e);
-    }
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/PostgreSQLConnectionHelper.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/PostgreSQLConnectionHelper.java
deleted file mode 100644
index 0c5387a..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/PostgreSQLConnectionHelper.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.util.db;
-
-import javax.sql.DataSource;
-
-/**
- * The connection helper for PSQL databases. It has special fetch size handling.
- */
-public final class PostgreSQLConnectionHelper extends ConnectionHelper {
-
-    /**
-     * @param dataSrc the {@code DataSource} on which this helper acts
-     * @param block whether to block on connection loss until the db is up again
-     */
-    public PostgreSQLConnectionHelper(DataSource dataSrc, boolean block) {
-        super(dataSrc, false, block, 10000);
-    }
-
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/ResultSetWrapper.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/ResultSetWrapper.java
deleted file mode 100644
index c51c7bf..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/ResultSetWrapper.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.util.db;
-
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.Statement;
-
-/**
- * This is a dynamic proxy in order to support both Java 5 and 6.
- */
-public final class ResultSetWrapper implements InvocationHandler {
-
-    private final Connection connection;
-
-    private final Statement statement;
-
-    private final ResultSet resultSet;
-
-    /**
-     * Creates a new {@code ResultSet} proxy which closes the given {@code Connection} and
-     * {@code Statement} if it is closed.
-     * 
-     * @param con the associated {@code Connection}
-     * @param stmt the associated {@code Statement}
-     * @param rs the {@code ResultSet} which backs the proxy
-     * @return a {@code ResultSet} proxy
-     */
-    public static final ResultSet newInstance(Connection con, Statement stmt, ResultSet rs) {
-        ResultSetWrapper proxy = new ResultSetWrapper(con, stmt, rs);
-        return (ResultSet) Proxy.newProxyInstance(rs.getClass().getClassLoader(),
-            new Class<?>[]{ResultSet.class}, proxy);
-    }
-
-    private ResultSetWrapper(Connection con, Statement stmt, ResultSet rs) {
-        connection = con;
-        statement = stmt;
-        resultSet = rs;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
-        if ("close".equals(m.getName())) {
-            DbUtility.close(connection, statement, resultSet);
-            return null;
-        } else {
-            return m.invoke(resultSet, args);
-        }
-    }
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/StreamWrapper.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/StreamWrapper.java
deleted file mode 100644
index 746803b..0000000
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/StreamWrapper.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.util.db;
-
-import java.io.InputStream;
-
-public class StreamWrapper {
-
-    private final InputStream stream;
-    private final long size;
-
-    /**
-     * Creates a wrapper for the given InputStream that can
-     * safely be passed as a parameter to the {@link ConnectionHelper#exec(String, Object...)},
-     * {@link ConnectionHelper#exec(String, Object[], boolean, int)} and
-     * {@link ConnectionHelper#update(String, Object[])} methods.
-     *
-     * @param in the InputStream to wrap
-     * @param size the size of the input stream
-     */
-    public StreamWrapper(InputStream in, long size) {
-        this.stream = in;
-        this.size = size;
-    }
-    
-    public InputStream getStream() {
-        return stream;
-    }
-    
-    public long getSize() {
-        return size;
-    }
-}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBFileValue.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBFileValue.java
index 8a94909..747c990 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBFileValue.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBFileValue.java
@@ -30,7 +30,7 @@ import org.apache.jackrabbit.core.data.DataStore;
  * Unlike <code>BinaryValue</code> it has no state, i.e.
  * the <code>getStream()</code> method always returns a fresh
  * <code>InputStream</code> instance.
- * <p/>
+ * <p>
  * <b>Important Note:</b><p/>
  * This interface is for Jackrabbit-internal use only. Applications should
  * use <code>javax.jcr.ValueFactory</code> to create binary values.
@@ -50,7 +50,7 @@ abstract class BLOBFileValue implements Binary {
      * this object. However an implementation must guarantee that the returned
      * value has state that is independent from this value. Immutable values
      * can savely return the same value (this object).
-     * <p/>
+     * <p>
      * Specifically, {@link #dispose()} on the returned value must not have an
      * effect on this value!
      *
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInDataStore.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInDataStore.java
index 86fbabf..e6730ce 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInDataStore.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/BLOBInDataStore.java
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.core.value;
 
+import org.apache.jackrabbit.api.ReferenceBinary;
 import org.apache.jackrabbit.core.data.DataIdentifier;
 import org.apache.jackrabbit.core.data.DataRecord;
 import org.apache.jackrabbit.core.data.DataStore;
@@ -30,7 +31,7 @@ import javax.jcr.RepositoryException;
 /**
  * Represents binary data which is stored in the data store.
  */
-class BLOBInDataStore extends BLOBFileValue {
+class BLOBInDataStore extends BLOBFileValue implements ReferenceBinary {
 
     private final DataStore store;
     private final DataIdentifier identifier;
@@ -45,6 +46,8 @@ class BLOBInDataStore extends BLOBFileValue {
      */
     private static Logger log = LoggerFactory.getLogger(BLOBInDataStore.class);
 
+    /** the audit logger */
+    private static Logger auditLogger = LoggerFactory.getLogger("org.apache.jackrabbit.core.audit");
 
     private BLOBInDataStore(DataStore store, DataIdentifier identifier) {
         assert store != null;
@@ -101,6 +104,16 @@ class BLOBInDataStore extends BLOBFileValue {
         return getDataRecord().getStream();
     }
 
+    @Override
+    public String getReference() {
+        try {
+            return getDataRecord().getReference();
+        } catch (DataStoreException e) {
+            log.warn("Unable to access the reference to " + identifier, e);
+            return null;
+        }
+    }
+
     public String toString() {
         return PREFIX + identifier;
     }
@@ -118,6 +131,9 @@ class BLOBInDataStore extends BLOBFileValue {
     static BLOBInDataStore getInstance(DataStore store, InputStream in) throws DataStoreException {
         DataRecord rec = store.addRecord(in);
         DataIdentifier identifier = rec.getIdentifier();
+        if (auditLogger.isDebugEnabled()) {
+            auditLogger.debug("{} ({})", identifier, rec.getLength());
+        }
         return new BLOBInDataStore(store, identifier);
     }
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValue.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValue.java
index a71168a..5f43682 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValue.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValue.java
@@ -25,7 +25,6 @@ import java.math.BigDecimal;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.Calendar;
-import java.util.TimeZone;
 
 import javax.jcr.Binary;
 import javax.jcr.PropertyType;
@@ -53,7 +52,7 @@ import org.apache.jackrabbit.util.ISO8601;
 
 /**
  * <code>InternalValue</code> represents the internal format of a property value.
- * <p/>
+ * <p>
  * The following table specifies the internal format for every property type:
  * <pre>
  * <table>
@@ -324,9 +323,7 @@ public class InternalValue extends AbstractQValue {
      * @return the created value
      */
     public static InternalValue createDate(String value) {
-        InternalValue iv = new InternalValue(Calendar.getInstance(TimeZone.getTimeZone("GMT+00:00")));
-        iv.val = value;
-        return iv;
+        return new InternalValue(value, PropertyType.DATE);
     }
 
     /**
@@ -643,23 +640,8 @@ public class InternalValue extends AbstractQValue {
         }
     }
 
-    /**
-     * Store a value in the data store. This will store temporary files or in-memory objects
-     * in the data store.
-     *
-     * @param dataStore the data store
-     * @throws RepositoryException
-     */
-    public void store(DataStore dataStore) throws RepositoryException {
-        assert dataStore != null;
-        assert type == PropertyType.BINARY;
-        BLOBFileValue v = (BLOBFileValue) val;
-        if (v instanceof BLOBInDataStore) {
-            // already in the data store, OK
-            return;
-        }
-        // store it in the data store
-        val = BLOBInDataStore.getInstance(dataStore, getStream());
+    public boolean isInDataStore() {
+        return val instanceof BLOBInDataStore;
     }
 
     //-------------------------------------------------------------< QValue >---
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValueFactory.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValueFactory.java
index b5b4eeb..6c5ae6b 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValueFactory.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/InternalValueFactory.java
@@ -25,6 +25,8 @@ import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.commons.value.AbstractQValueFactory;
 
 import javax.jcr.RepositoryException;
+
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -92,7 +94,11 @@ public final class InternalValueFactory extends AbstractQValueFactory {
     }
 
     public QValue create(byte[] value) throws RepositoryException {
-        return InternalValue.create(value);
+        if (store == null) {
+            return InternalValue.create(value);
+        } else {
+            return InternalValue.create(new ByteArrayInputStream(value), store);
+        }
     }
 
     public QValue create(InputStream value) throws RepositoryException, IOException {
@@ -105,7 +111,11 @@ public final class InternalValueFactory extends AbstractQValueFactory {
 
     public QValue create(File value) throws RepositoryException, IOException {
         InputStream in = new FileInputStream(value);
-        return InternalValue.createTemporary(in);
+        if (store == null) {
+            return InternalValue.createTemporary(in);
+        } else {
+            return InternalValue.create(in, store);
+        }
     }
 
     @Override
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/ValueFactoryImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/ValueFactoryImpl.java
index b16be96..460956b 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/ValueFactoryImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/value/ValueFactoryImpl.java
@@ -17,7 +17,9 @@
 package org.apache.jackrabbit.core.value;
 
 import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.api.ReferenceBinary;
 import org.apache.jackrabbit.core.data.DataIdentifier;
+import org.apache.jackrabbit.core.data.DataRecord;
 import org.apache.jackrabbit.core.data.DataStore;
 import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
 import org.apache.jackrabbit.spi.commons.value.ValueFactoryQImpl;
@@ -106,6 +108,13 @@ public class ValueFactoryImpl extends ValueFactoryQImpl {
                 }
             } else if (binary instanceof BLOBFileValue) {
                 return new BinaryValueImpl(((BLOBFileValue) binary).copy());
+            } else if (binary instanceof ReferenceBinary) {
+                String reference = ((ReferenceBinary) binary).getReference();
+                DataRecord record = store.getRecordFromReference(reference);
+                if (record != null) {
+                    return new BinaryValueImpl(BLOBInDataStore.getInstance(
+                            store, record.getIdentifier()));
+                }
             }
             return createValue(binary.getStream());
         } catch (RepositoryException e) {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalBaseline.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalBaseline.java
index 07b0441..ab1b054 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalBaseline.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalBaseline.java
@@ -22,12 +22,12 @@ import org.apache.jackrabbit.core.id.NodeId;
 
 /**
  * This interface defines the internal baseline.
- * <p/>
+ * <p>
  * A baseline is the state of a configuration at some point in time, recorded in
  * version storage. A baseline is similar to a normal version except that
  * instead of representing the state of a single node, it represents the state
  * of an entire partial subgraph.
- * <p/>
+ * <p>
  * The internal baseline is the version of the internal configuration.
  */
 public interface InternalBaseline extends InternalVersion {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalFrozenNodeImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalFrozenNodeImpl.java
index ab4519d..d1be5c4 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalFrozenNodeImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalFrozenNodeImpl.java
@@ -265,7 +265,11 @@ class InternalFrozenNodeImpl extends InternalFreezeImpl
                 // ignore frozen properties
                 if (!propName.equals(NameConstants.JCR_PRIMARYTYPE)
                         && !propName.equals(NameConstants.JCR_MIXINTYPES)
-                        && !propName.equals(NameConstants.JCR_UUID)) {
+                        && !propName.equals(NameConstants.JCR_UUID)
+                        // JCR-3635: should never occur in normal content...
+                        && !propName.equals(NameConstants.JCR_FROZENPRIMARYTYPE)
+                        && !propName.equals(NameConstants.JCR_FROZENMIXINTYPES)
+                        && !propName.equals(NameConstants.JCR_FROZENUUID)) {
                     node.copyFrom(prop);
                 }
             }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalFrozenVersionHistory.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalFrozenVersionHistory.java
index 6cbabef..05d3c22 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalFrozenVersionHistory.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalFrozenVersionHistory.java
@@ -53,7 +53,7 @@ public interface InternalFrozenVersionHistory extends InternalFreeze {
     NodeId getBaseVersionId();
 
     /**
-     * @deprecate use {@link #getBaseVersion()} instead
+     * @deprecated use {@link #getBaseVersion()} instead
      */
     InternalVersion getBaseVesion() throws VersionException;
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionHistoryImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionHistoryImpl.java
index c3e42d3..c3ec82b 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionHistoryImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionHistoryImpl.java
@@ -391,7 +391,7 @@ class InternalVersionHistoryImpl extends InternalVersionItemImpl
      * Removes the indicated version from this VersionHistory. If the specified
      * vesion does not exist, if it specifies the root version or if it is
      * referenced by any node e.g. as base version, a VersionException is thrown.
-     * <p/>
+     * <p>
      * all successors of the removed version become successors of the
      * predecessors of the removed version and vice versa. then, the entire
      * version node and all its subnodes are removed.
@@ -665,7 +665,8 @@ class InternalVersionHistoryImpl extends InternalVersionItemImpl
             node.setPropertyValues(NameConstants.JCR_FROZENMIXINTYPES, PropertyType.NAME, ivalues);
         }
 
-        parent.store();
+        parent.store(false);
+        pNode.store(true);
         return pNode;
     }
 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionImpl.java
index ec79ec0..34c7ad3 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionImpl.java
@@ -338,7 +338,7 @@ class InternalVersionImpl extends InternalVersionItemImpl
     /**
      * Removes the predecessor V of this predecessors list and adds all of Vs
      * predecessors to it.
-     * <p/>
+     * <p>
      * please note, that this operation might corrupt the version graph
      *
      * @param v the successor to detach
@@ -359,7 +359,7 @@ class InternalVersionImpl extends InternalVersionItemImpl
     /**
      * Removes the successor V of this successors list and adds all of Vs
      * successors to it.
-     * <p/>
+     * <p>
      * please note, that this operation might corrupt the version graph
      *
      * @param v the successor to detach
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionManager.java
index 3cb7e1a..fc003b8 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionManager.java
@@ -92,6 +92,15 @@ public interface InternalVersionManager {
             throws RepositoryException;
 
     /**
+     * Removes the specified version history from storage.
+     *
+     * @param session the session that performs the remove
+     * @param history the version history to remove
+     * @throws RepositoryException if an error occurs
+     */
+    void removeVersionHistory(Session session, InternalVersionHistory history) throws RepositoryException;
+
+    /**
      * Sets the version <code>label</code> to the given <code>version</code>.
      * If the label is already assigned to another version, a VersionException is
      * thrown unless <code>move</code> is <code>true</code>. If <code>version</code>
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionManagerBase.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionManagerBase.java
index ece1702..14b509f 100755
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionManagerBase.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionManagerBase.java
@@ -46,7 +46,7 @@ import org.slf4j.LoggerFactory;
 
 /**
  * Base implementation of the {@link InternalVersionManager} interface.
- * <p/>
+ * <p>
  * All read operations must acquire the read lock before reading, all write
  * operations must acquire the write lock.
  */
@@ -308,7 +308,7 @@ abstract class InternalVersionManagerBase implements InternalVersionManager {
      * Returns information about the version history of the specified node
      * or <code>null</code> when unavailable.
      *
-     * @param vNode node whose version history should be returned
+     * @param node node whose version history should be returned
      * @return identifiers of the version history and root version nodes
      * @throws RepositoryException if an error occurs
      */
@@ -375,7 +375,7 @@ abstract class InternalVersionManagerBase implements InternalVersionManager {
 
     /**
      * Returns the item with the given persistent id. Subclass responsibility.
-     * <p/>
+     * <p>
      * Please note, that the overridden method must acquire the readlock before
      * reading the state manager.
      *
@@ -398,7 +398,7 @@ abstract class InternalVersionManagerBase implements InternalVersionManager {
     /**
      * Checks if there are item references (from outside the version storage)
      * that reference the given node. Subclass responsibility.
-     * <p/>
+     * <p>
      * Please note, that the overridden method must acquire the readlock before
      * reading the state manager.
      *
@@ -413,7 +413,7 @@ abstract class InternalVersionManagerBase implements InternalVersionManager {
 
     /**
      * Returns the node with the given persistent id. Subclass responsibility.
-     * <p/>
+     * <p>
      * Please note, that the overridden method must acquire the readlock before
      * reading the state manager.
      *
@@ -576,7 +576,8 @@ abstract class InternalVersionManagerBase implements InternalVersionManager {
                 n = childn;
             } else if (interNT != null) {
                 childn = n.addNode(name, interNT, null, false);
-                n.store();
+                n.store(false);
+                childn.store(true);
                 n = childn;
             } else {
                 return null;
@@ -699,6 +700,13 @@ abstract class InternalVersionManagerBase implements InternalVersionManager {
     protected String calculateCheckinVersionName(InternalVersionHistoryImpl history,
                                                  NodeStateEx node, boolean simple)
             throws RepositoryException {
+
+        if (history == null) {
+            String message = "Node " + node.getNodeId() + " has no version history";
+            log.error(message);
+            throw new VersionException(message);
+        }
+
         InternalVersion best = null;
         if (simple) {
             // 1. in simple versioning just take the 'head' version
@@ -708,20 +716,37 @@ abstract class InternalVersionManagerBase implements InternalVersionManager {
             // 1. search a predecessor, suitable for generating the new name
             InternalValue[] values = node.getPropertyValues(NameConstants.JCR_PREDECESSORS);
 
-            if (values == null) {
-                String message = "Mandatory jcr:predecessors property missing on node " + node.getNodeId();
+            if (values == null || values.length == 0) {
+                String message;
+                if (values == null) {
+                    message = "Mandatory jcr:predecessors property missing on node " + node.getNodeId();
+                } else {
+                    message = "Mandatory jcr:predecessors property is empty on node " + node.getNodeId();
+                }
                 log.error(message);
                 throw new VersionException(message);
             }
 
             for (InternalValue value: values) {
                 InternalVersion pred = history.getVersion(value.getNodeId());
+                if (pred == null) {
+                    String message = "Could not instantiate InternalVersion for nodeId " + value.getNodeId() + " (VHR + " + history.getId() + ", node " + node.getNodeId() + ")";
+                    log.error(message);
+                    throw new VersionException(message);
+                }
                 if (best == null
                         || pred.getName().getLocalName().length() < best.getName().getLocalName().length()) {
                     best = pred;
                 }
             }
         }
+
+        if (best == null) {
+            String message = "Could not find 'best' predecessor node for " + node.getNodeId();
+            log.error(message);
+            throw new VersionException(message);
+        }
+
         // 2. generate version name (assume no namespaces in version names)
         String versionName = best.getName().getLocalName();
         int pos = versionName.lastIndexOf('.');
@@ -762,6 +787,29 @@ abstract class InternalVersionManagerBase implements InternalVersionManager {
     }
 
     /**
+     * Removes the specified history from the storage
+     *
+     * @param history the version history to remove
+     * @throws VersionException
+     * @throws RepositoryException
+     */
+    public void internalRemoveVersionHistory(InternalVersionHistoryImpl history)
+            throws VersionException, RepositoryException {
+        String versionableUuid = history.getVersionableId().toString();
+        WriteOperation operation = startWriteOperation();
+        try {
+            NodeStateEx parent = getParentNode(getHistoryRoot(), versionableUuid, null);
+            parent.removeNode(history.node.getName());
+            parent.store();
+            operation.save();
+        } catch (ItemStateException e) {
+            log.error("Error while storing: " + e.toString());
+        } finally {
+            operation.close();
+        }
+    }
+
+    /**
      * Set version label on the specified version.
      *
      * @param history version history
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionManagerImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionManagerImpl.java
index c4ff400..b137448 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionManagerImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalVersionManagerImpl.java
@@ -243,7 +243,7 @@ public class InternalVersionManagerImpl extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * This method must not be synchronized since it could cause deadlocks with
      * item-reading listeners in the observation thread.
      */
@@ -270,7 +270,7 @@ public class InternalVersionManagerImpl extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * This method must not be synchronized since it could cause deadlocks with
      * item-reading listeners in the observation thread.
      */
@@ -286,7 +286,7 @@ public class InternalVersionManagerImpl extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * This method must not be synchronized since it could cause deadlocks with
      * item-reading listeners in the observation thread.
      */
@@ -402,7 +402,7 @@ public class InternalVersionManagerImpl extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * This method must not be synchronized since it could cause deadlocks with
      * item-reading listeners in the observation thread.
      */
@@ -420,7 +420,7 @@ public class InternalVersionManagerImpl extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * This method must not be synchronized since it could cause deadlocks with
      * item-reading listeners in the observation thread.
      */
@@ -444,7 +444,22 @@ public class InternalVersionManagerImpl extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
+     * This method must not be synchronized since it could cause deadlocks with
+     * item-reading listeners in the observation thread.
+     */
+    public void removeVersionHistory(Session session, final InternalVersionHistory history) throws RepositoryException {
+        escFactory.doSourced((SessionImpl) session, new SourcedTarget() {
+            public Object run() throws RepositoryException {
+                internalRemoveVersionHistory((InternalVersionHistoryImpl) history);
+                return null;
+            }
+        });
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
      * This method must not be synchronized since it could cause deadlocks with
      * item-reading listeners in the observation thread.
      */
@@ -591,7 +606,7 @@ public class InternalVersionManagerImpl extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Not used.
      */
     public void stateCreated(ItemState created) {
@@ -599,7 +614,7 @@ public class InternalVersionManagerImpl extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Not used.
      */
     public void stateModified(ItemState modified) {
@@ -607,7 +622,7 @@ public class InternalVersionManagerImpl extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Remove item from cache on removal.
      */
     public void stateDestroyed(ItemState destroyed) {
@@ -622,7 +637,7 @@ public class InternalVersionManagerImpl extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Not used.
      */
     public void stateDiscarded(ItemState discarded) {
@@ -681,7 +696,7 @@ public class InternalVersionManagerImpl extends InternalVersionManagerBase
 
         /**
          * {@inheritDoc}
-         * <p/>
+         * <p>
          * This object uses one instance of a <code>LocalItemStateManager</code>
          * to update data on behalf of many sessions. In order to maintain the
          * association between update operation and session who actually invoked
@@ -699,7 +714,7 @@ public class InternalVersionManagerImpl extends InternalVersionManagerBase
 
         /**
          * {@inheritDoc}
-         * <p/>
+         * <p>
          * This object uses one instance of a <code>LocalItemStateManager</code>
          * to update data on behalf of many sessions. In order to maintain the
          * association between update operation and session who actually invoked
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalXAVersionManager.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalXAVersionManager.java
index fc60dc3..a9032e5 100755
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalXAVersionManager.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/InternalXAVersionManager.java
@@ -24,10 +24,7 @@ import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.version.VersionException;
 
-import org.apache.jackrabbit.core.InternalXAResource;
 import org.apache.jackrabbit.core.SessionImpl;
-import org.apache.jackrabbit.core.TransactionContext;
-import org.apache.jackrabbit.core.TransactionException;
 import org.apache.jackrabbit.core.id.ItemId;
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
@@ -45,6 +42,9 @@ import org.apache.jackrabbit.core.state.XAItemStateManager;
 import org.apache.jackrabbit.core.virtual.VirtualItemStateProvider;
 import org.apache.jackrabbit.core.virtual.VirtualNodeState;
 import org.apache.jackrabbit.core.virtual.VirtualPropertyState;
+import org.apache.jackrabbit.data.core.InternalXAResource;
+import org.apache.jackrabbit.data.core.TransactionContext;
+import org.apache.jackrabbit.data.core.TransactionException;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
 
@@ -206,7 +206,7 @@ public class InternalXAVersionManager extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Before modifying activity, make a local copy of it.
      */
     @Override
@@ -255,6 +255,18 @@ public class InternalXAVersionManager extends InternalVersionManagerBase
     /**
      * {@inheritDoc}
      */
+    public void removeVersionHistory(Session session, InternalVersionHistory history)
+            throws RepositoryException {
+        if (isInXA()) {
+            internalRemoveVersionHistory((InternalVersionHistoryImpl) history);
+        } else {
+            vMgr.removeVersionHistory(session, history);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     public InternalVersion setVersionLabel(Session session,
                                            InternalVersionHistory history,
                                            Name version,
@@ -336,7 +348,7 @@ public class InternalXAVersionManager extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Return item states for changes only. Global version manager will return
      * other items.
      */
@@ -387,7 +399,7 @@ public class InternalXAVersionManager extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Not needed.
      */
     public void addListener(ItemStateListener listener) {
@@ -395,7 +407,7 @@ public class InternalXAVersionManager extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Not needed.
      */
     public void removeListener(ItemStateListener listener) {
@@ -470,7 +482,7 @@ public class InternalXAVersionManager extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Before modifying version history given, make a local copy of it.
      */
     @Override
@@ -495,7 +507,7 @@ public class InternalXAVersionManager extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Before modifying version history given, make a local copy of it.
      */
     @Override
@@ -519,7 +531,7 @@ public class InternalXAVersionManager extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Before modifying version history given, make a local copy of it.
      */
     @Override
@@ -537,7 +549,7 @@ public class InternalXAVersionManager extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Put the version object into our cache.
      */
     @Override
@@ -547,7 +559,7 @@ public class InternalXAVersionManager extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Remove the version object from our cache.
      */
     @Override
@@ -577,7 +589,7 @@ public class InternalXAVersionManager extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Delegate the call to our XA item state manager.
      */
     public void beforeOperation(TransactionContext tx) {
@@ -586,7 +598,7 @@ public class InternalXAVersionManager extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Delegate the call to our XA item state manager.
      */
     public void prepare(TransactionContext tx) throws TransactionException {
@@ -597,7 +609,7 @@ public class InternalXAVersionManager extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Delegate the call to our XA item state manager. If successful, inform
      * global repository manager to update its caches.
      */
@@ -613,7 +625,7 @@ public class InternalXAVersionManager extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Delegate the call to our XA item state manager.
      */
     public void rollback(TransactionContext tx) {
@@ -624,7 +636,7 @@ public class InternalXAVersionManager extends InternalVersionManagerBase
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Delegate the call to our XA item state manager.
      */
     public void afterOperation(TransactionContext tx) {
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/NodeStateEx.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/NodeStateEx.java
index f9706e1..63e7161 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/NodeStateEx.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/NodeStateEx.java
@@ -279,6 +279,7 @@ public class NodeStateEx {
                     nodeState.setStatus(ItemState.STATUS_EXISTING_MODIFIED);
                 }
                 propState.setType(type);
+                propState.setMultiValued(multiple);
                 propState.setValues(values);
                 return propState;
             } catch (ItemStateException e) {
@@ -710,8 +711,21 @@ public class NodeStateEx {
      * @throws RepositoryException if an error occurs
      */
     public void store() throws RepositoryException {
+        store(true);
+    }
+
+    /**
+     * Stores the persistent state and depending on the <code>recursively</code>
+     * flag also stores the modified child nodes recursively.
+     *
+     *
+     * @param recursively whether to store the nodes recursively or just this
+     *                    single node.
+     * @throws RepositoryException if an error occurs
+     */
+    public void store(boolean recursively) throws RepositoryException {
         try {
-            store(nodeState);
+            store(nodeState, recursively);
         } catch (ItemStateException e) {
             throw new RepositoryException(e);
         }
@@ -723,7 +737,7 @@ public class NodeStateEx {
      * @param state node state to store
      * @throws ItemStateException if an error occurs
      */
-    private void store(NodeState state)
+    private void store(NodeState state, boolean recursively)
             throws ItemStateException {
 
         if (state.getStatus() != ItemState.STATUS_EXISTING) {
@@ -735,10 +749,12 @@ public class NodeStateEx {
                     stateMgr.store(pstate);
                 }
             }
-            // now store all child node entries
-            for (ChildNodeEntry entry : state.getChildNodeEntries()) {
-                NodeState nstate = (NodeState) stateMgr.getItemState(entry.getId());
-                store(nstate);
+            if (recursively) {
+                // now store all child node entries
+                for (ChildNodeEntry entry : state.getChildNodeEntries()) {
+                    NodeState nstate = (NodeState) stateMgr.getItemState(entry.getId());
+                    store(nstate, true);
+                }
             }
             // and store itself
             stateMgr.store(state);
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionHistoryImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionHistoryImpl.java
index 856dbe7..770cac2 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionHistoryImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionHistoryImpl.java
@@ -269,6 +269,20 @@ public class VersionHistoryImpl extends NodeImpl implements VersionHistory {
     }
 
     /**
+     * Removes this VersionHistory from storage.
+     *
+     * @throws RepositoryException if an error occurs.
+     */
+    public void removeVersionHistory() throws RepositoryException {
+        checkVersionManagementPermission();
+        InternalVersionManager internalVersionManager =
+                sessionContext.getSessionImpl().getInternalVersionManager();
+        internalVersionManager.removeVersionHistory(
+                getSession(),
+                getInternalVersionHistory());
+    }
+
+    /**
      * @see javax.jcr.Item#isSame(javax.jcr.Item)
      */
     @Override
@@ -313,7 +327,7 @@ public class VersionHistoryImpl extends NodeImpl implements VersionHistory {
             // ignore.
         }
     }
-    
+
     /**
      * Checks if the given version belongs to this history
      *
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionIteratorImpl.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionIteratorImpl.java
index c9d29c1..5110f42 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionIteratorImpl.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionIteratorImpl.java
@@ -33,7 +33,7 @@ import java.util.NoSuchElementException;
  * the id's of the versions and returns them when iterating. please note, that
  * a version can be deleted while traversing this iterator and the 'nextVersion'
  * would produce a  ConcurrentModificationException.
- * <p/>
+ * <p>
  * If this iterator is initialized with a base version, it will only iterate
  * on the versions of a single line of decent from the given root version to the
  * base version.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionManagerImplBase.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionManagerImplBase.java
index 283fe20..67547cb 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionManagerImplBase.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionManagerImplBase.java
@@ -317,7 +317,7 @@ abstract public class VersionManagerImplBase {
 
     /**
      * Determines the checked-out status of the given node state.
-     * <p/>
+     * <p>
      * A node is considered <i>checked-out</i> if it is versionable and
      * checked-out, or is non-versionable but its nearest versionable ancestor
      * is checked-out, or is non-versionable and there are no versionable
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionManagerImplConfig.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionManagerImplConfig.java
index b36d4bc..56c86b4 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionManagerImplConfig.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionManagerImplConfig.java
@@ -38,9 +38,9 @@ import org.slf4j.Logger;
 /**
  * The JCR Version Manager implementation is split in several classes in order to
  * group related methods together.
- * <p/>
+ * <p>
  * this class provides methods for the configuration and baselines related operations.
- * <p/>
+ * <p>
  * Implementation note: methods starting with "internal" are considered to be
  * executed within a "write operations" block.
  */
@@ -130,7 +130,7 @@ abstract public class VersionManagerImplConfig extends VersionManagerImplMerge {
 
     /**
      * Creates a new configuration node.
-     * <p/>
+     * <p>
      * The nt:confguration is stored within the nt:configurations storage using
      * the nodeid of the configuration root (rootId) as path.
      *
@@ -159,7 +159,7 @@ abstract public class VersionManagerImplConfig extends VersionManagerImplMerge {
 
     /**
      * Creates a new configuration node.
-     * <p/>
+     * <p>
      * The nt:confguration is stored within the nt:configurations storage using
      * the nodeid of the configuration root (rootId) as path.
      *
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionManagerImplMerge.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionManagerImplMerge.java
index c0242d0..995d1f7 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionManagerImplMerge.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionManagerImplMerge.java
@@ -92,7 +92,8 @@ abstract public class VersionManagerImplMerge extends VersionManagerImplRestore
         if (shallow) {
             // If <code>isShallow</code> is <code>true</code> and this node is not
             // versionable, then this method returns and no changes are made.
-            if (!state.getEffectiveNodeType().includesNodeType(NameConstants.MIX_VERSIONABLE)) {
+            if (!state.getEffectiveNodeType().includesNodeType(
+                    NameConstants.MIX_SIMPLE_VERSIONABLE)) {
                 return;
             }
         }
@@ -208,7 +209,7 @@ abstract public class VersionManagerImplMerge extends VersionManagerImplRestore
 
     /**
      * Returns the corresponding node in the workspace of the given session.
-     * <p/>
+     * <p>
      * Given a node N1 in workspace W1, its corresponding node N2 in workspace
      * W2 is defined as follows:
      * <ul>
@@ -261,7 +262,7 @@ abstract public class VersionManagerImplMerge extends VersionManagerImplRestore
      * then <code>null</code> is returned. If the result of the merge test is
      * 'fail' with bestEffort set to <code>false</code> a MergeException is
      * thrown.
-     * <p/>
+     * <p>
      * jsr170 - 8.2.10 Merge:
      * [...]
      * <ul>
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionManagerImplRestore.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionManagerImplRestore.java
index 5b16ebc..1082f0d 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionManagerImplRestore.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionManagerImplRestore.java
@@ -48,9 +48,9 @@ import org.slf4j.LoggerFactory;
 /**
  * The JCR Version Manager implementation is split in several classes in order to
  * group related methods together.
- * <p/>
+ * <p>
  * this class provides methods for the restore operations.
- * <p/>
+ * <p>
  * Implementation note: methods starting with "internal" are considered to be
  * executed within a "write operations" block.
  */
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionSelector.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionSelector.java
index ff6c135..5775551 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionSelector.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersionSelector.java
@@ -25,7 +25,7 @@ import javax.jcr.RepositoryException;
  * OPV=Version children upon restore. JSR170 states: <em>"This determination
  * [of the version] depends on the configuration of the workspace and is outside
  * the scope of this specification."</em>
- * <p/>
+ * <p>
  * The version selection in jackrabbit works as follows:<br/>
  * The <code>Node.restore()</code> methods uses the
  * {@link DateVersionSelector} which is initialized with the creation date of
@@ -36,7 +36,7 @@ import javax.jcr.RepositoryException;
  * {@link LabelVersionSelector} which is initialized with the label of the
  * version to be restored. This selector selects the version with the same
  * label. if no such version exists, the initial one is restored.
- * <p/>
+ * <p>
  *
  * @see DateVersionSelector
  * @see LabelVersionSelector
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersioningLock.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersioningLock.java
index c9a9a77..8f58e32 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersioningLock.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/version/VersioningLock.java
@@ -16,14 +16,9 @@
  */
 package org.apache.jackrabbit.core.version;
 
-import java.util.Arrays;
-
-import javax.transaction.xa.Xid;
-
-import org.apache.jackrabbit.core.TransactionContext;
+import org.apache.jackrabbit.core.util.XAReentrantWriterPreferenceReadWriteLock;
 
 import EDU.oswego.cs.dl.util.concurrent.ReadWriteLock;
-import EDU.oswego.cs.dl.util.concurrent.ReentrantWriterPreferenceReadWriteLock;
 import EDU.oswego.cs.dl.util.concurrent.Sync;
 
 /**
@@ -36,30 +31,15 @@ public class VersioningLock {
 
     /**
      * The internal read-write lock.
-     * Thread concerning ReentrantWriterPreferenceReadWriteLock
-     */
-    private final ReadWriteLock rwLock =
-        new ReentrantWriterPreferenceReadWriteLock();
-
-    /**
-     * The internal Xid aware read-write lock.
      */
-    private final ReadWriteLock xidRwLock = new XidRWLock();
+    private final XAReentrantWriterPreferenceReadWriteLock rwLock = new XAReentrantWriterPreferenceReadWriteLock();
 
     public ReadLock acquireReadLock() throws InterruptedException {
-        if (TransactionContext.getCurrentXid() == null) {
-            return new ReadLock(rwLock.readLock());
-        } else {
-            return new ReadLock(xidRwLock.readLock());
-        }
+    	return new ReadLock(rwLock.readLock());
     }
 
     public WriteLock acquireWriteLock() throws InterruptedException {
-        if (TransactionContext.getCurrentXid() == null) {
-            return new WriteLock(rwLock);
-        } else {
-            return new WriteLock(xidRwLock);
-        }
+    	return new WriteLock(rwLock);
     }
 
     public static class WriteLock {
@@ -98,127 +78,4 @@ public class VersioningLock {
         }
 
     }
-
-    /**
-     * Xid concerning ReentrantWriterPreferenceReadWriteLock
-     */
-    private static final class XidRWLock
-            extends ReentrantWriterPreferenceReadWriteLock {
-
-        private Xid activeXid;
-
-        /**
-         * Check if the given Xid comes from the same globalTX
-         * @param otherXid
-         * @return true if same globalTX otherwise false
-         */
-        boolean isSameGlobalTx(Xid otherXid) {
-            return (activeXid == otherXid) || Arrays.equals(activeXid.getGlobalTransactionId(), otherXid.getGlobalTransactionId());
-        }
-
-        /**
-         * Allow reader when there is no active Xid, or current Xid owns
-         * the write lock (reentrant).
-         */
-        @Override
-        protected boolean allowReader() {
-            Xid currentXid = TransactionContext.getCurrentXid();
-            return (activeXid == null && waitingWriters_ == 0) || isSameGlobalTx(currentXid);
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        protected synchronized boolean startWrite() {
-            Xid currentXid = TransactionContext.getCurrentXid();
-            if (activeXid != null && isSameGlobalTx(currentXid)) { // already held; re-acquire
-                ++writeHolds_;
-                return true;
-            } else if (writeHolds_ == 0) {
-                if (activeReaders_ == 0 || (readers_.size() == 1 && readers_.get(currentXid) != null)) {
-                    activeXid = currentXid;
-                    writeHolds_ = 1;
-                    return true;
-                } else {
-                    return false;
-                }
-            } else {
-                return false;
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        protected synchronized Signaller endWrite() {
-            --writeHolds_;
-            if (writeHolds_ > 0) {  // still being held
-                return null;
-            } else {
-                activeXid = null;
-                if (waitingReaders_ > 0 && allowReader()) {
-                    return readerLock_;
-                } else if (waitingWriters_ > 0) {
-                    return writerLock_;
-                } else {
-                    return null;
-                }
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        @SuppressWarnings("unchecked")
-        protected synchronized boolean startRead() {
-            Xid currentXid = TransactionContext.getCurrentXid();
-            Object c = readers_.get(currentXid);
-            if (c != null) { // already held -- just increment hold count
-                readers_.put(currentXid, (Integer) (c) + 1);
-                ++activeReaders_;
-                return true;
-            } else if (allowReader()) {
-                readers_.put(currentXid, IONE);
-                ++activeReaders_;
-                return true;
-            } else {
-                return false;
-            }
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        @SuppressWarnings("unchecked")
-        protected synchronized Signaller endRead() {
-            Xid currentXid = TransactionContext.getCurrentXid();
-            Object c = readers_.get(currentXid);
-            if (c == null) {
-                throw new IllegalStateException();
-            }
-            --activeReaders_;
-            if (c != IONE) { // more than one hold; decrement count
-                int h = (Integer) (c) -1;
-                Integer ih = (h == 1)? IONE : new Integer(h);
-                readers_.put(currentXid, ih);
-                return null;
-            } else {
-                readers_.remove(currentXid);
-
-                if (writeHolds_ > 0) { // a write lock is still held
-                    return null;
-                } else if (activeReaders_ == 0 && waitingWriters_ > 0) {
-                    return writerLock_;
-                } else  {
-                    return null;
-                }
-            }
-        }
-
-    }
-
 }
\ No newline at end of file
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/AccessControlImporter.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/AccessControlImporter.java
index 2fcdb0a..de2e7c6 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/AccessControlImporter.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/AccessControlImporter.java
@@ -44,6 +44,7 @@ import org.apache.jackrabbit.api.JackrabbitSession;
 import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
 import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
 import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.security.principal.PrincipalImpl;
 import org.apache.jackrabbit.core.util.ReferenceChangeTracker;
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.security.authorization.AccessControlConstants;
@@ -89,6 +90,9 @@ public class AccessControlImporter extends DefaultProtectedNodeImporter {
 
     private boolean initialized = false;
 
+    // keep best-effort for backward compatibility reasons
+    private ImportBehavior importBehavior = ImportBehavior.BEST_EFFORT;
+
     /**
      * the ACL for non-principal based
      */
@@ -367,8 +371,13 @@ public class AccessControlImporter extends DefaultProtectedNodeImporter {
                 String pName = values[0].getString();
                 principal = session.getPrincipalManager().getPrincipal(pName);
                 if (principal == null) {
-                    // create "fake" principal
-                    principal = new UnknownPrincipal(pName);
+                    if (importBehavior == ImportBehavior.BEST_EFFORT) {
+                        // create "fake" principal that is always accepted in ACLTemplate.checkValidEntry()
+                        principal = new UnknownPrincipal(pName);
+                    } else {
+                        // create "fake" principal. this is checked again in ACLTemplate.checkValidEntry()
+                        principal = new PrincipalImpl(pName);
+                    }
                 }
             } else if (AccessControlConstants.P_PRIVILEGES.equals(name)) {
                 Value[] values = pInfo.getValues(PropertyType.NAME, resolver);
@@ -429,4 +438,54 @@ public class AccessControlImporter extends DefaultProtectedNodeImporter {
         // could not apply the ACE. No suitable ACL found.
         throw new ConstraintViolationException("Cannot handle childInfo " + childInfo + "; No policy found to apply the ACE.");        
     }
+
+    //---------------------------------------------------------< BeanConfig >---
+    /**
+     * @return human readable representation of the <code>importBehavior</code> value.
+     */
+    public String getImportBehavior() {
+        return importBehavior.getString();
+    }
+
+    /**
+     *
+     * @param importBehaviorStr
+     */
+    public void setImportBehavior(String importBehaviorStr) {
+        this.importBehavior = ImportBehavior.fromString(importBehaviorStr);
+    }
+
+
+    public static enum ImportBehavior {
+
+        /**
+         * Default behavior that does not try to prevent errors or incompatibilities between the content
+         * and the ACL manager (eg. does not try to fix missing principals)
+         */
+        DEFAULT("default"),
+
+        /**
+         * Tries to minimize errors by adapting the content and bypassing validation checks (e.g. allows adding
+         * ACEs with missing principals, even if ACL manager would not allow this).
+         */
+        BEST_EFFORT("bestEffort");
+
+        private final String value;
+
+        ImportBehavior(String value) {
+            this.value = value;
+        }
+
+        public static ImportBehavior fromString(String str) {
+            if (str.equals("bestEffort")) {
+                return BEST_EFFORT;
+            } else {
+                return ImportBehavior.valueOf(str.toUpperCase());
+            }
+        }
+
+        public String getString() {
+            return value;
+        }
+    }
 }
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/BufferedStringValue.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/BufferedStringValue.java
index 21ec82f..4ada862 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/BufferedStringValue.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/BufferedStringValue.java
@@ -50,7 +50,7 @@ import java.io.Writer;
  * <code>BufferedStringValue</code> represents an appendable
  * serialized value that is either buffered in-memory or backed
  * by a temporary file if its size exceeds a certain limit.
- * <p/>
+ * <p>
  * <b>Important:</b> Note that in order to free resources
  * <code>{@link #dispose()}</code> should be called as soon as
  * <code>BufferedStringValue</code> instance is not used anymore.
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ClonedInputSource.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ClonedInputSource.java
index 9589ad6..f8639fd 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ClonedInputSource.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ClonedInputSource.java
@@ -1,168 +1,168 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.xml;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.CharArrayReader;
-import java.io.CharArrayWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-
-import javax.jcr.RepositoryException;
-
-import org.xml.sax.InputSource;
-
-/**
- * Input source that clones existing input source. After cloning the existing
- * input source should not be used anymore. To make more copies call
- * {@link #cloneInputSource()} method.
- */
-public class ClonedInputSource extends InputSource {
-    private final char[] characterArray;
-    private final byte[] byteArray;
-
-    /**
-     * Clone existing input source.
-     *
-     * @param input
-     * @throws RepositoryException
-     */
-    public ClonedInputSource(InputSource input) throws RepositoryException {
-
-        if (input == null) {
-            throw new IllegalArgumentException(
-                    "Argument 'input' may not be null.");
-        }
-
-        characterArray = read(input.getCharacterStream());
-        byteArray = read(input.getByteStream());
-
-        setEncoding(input.getEncoding());
-        setPublicId(input.getPublicId());
-        setSystemId(input.getSystemId());
-        if (characterArray != null) {
-            setCharacterStream(new CharArrayReader(characterArray));
-        }
-        if (byteArray != null) {
-            setByteStream(new ByteArrayInputStream(byteArray));
-        }
-    }
-
-    private ClonedInputSource(char[] characterArray, byte[] byteArray) {
-        super();
-        this.characterArray = characterArray;
-        this.byteArray = byteArray;
-    }
-
-    public char[] getCharacterArray() {
-        return characterArray;
-    }
-
-    public byte[] getByteArray() {
-        return byteArray;
-    }
-
-    /**
-     * Make a clone if this input source. The input source being cloned is still
-     * valid after cloning.
-     *
-     * @return input source clone.
-     */
-    public ClonedInputSource cloneInputSource() {
-
-        ClonedInputSource res = new ClonedInputSource(characterArray, byteArray);
-
-        res.setEncoding(getEncoding());
-        res.setPublicId(getPublicId());
-        res.setSystemId(getSystemId());
-
-        if (byteArray != null) {
-            res.setByteStream(new ByteArrayInputStream(byteArray));
-        }
-        if (characterArray != null) {
-            res.setCharacterStream(new CharArrayReader(characterArray));
-        }
-
-        return res;
-    }
-
-    private static byte[] read(InputStream stream) throws RepositoryException {
-        if (stream != null) {
-            try {
-                final int bufferSize = Math.min(stream.available(), 4096);
-                ByteArrayOutputStream s = new ByteArrayOutputStream(bufferSize);
-
-                byte[] buffer = new byte[bufferSize];
-                while (true) {
-                    int numRead = stream.read(buffer);
-                    if (numRead > 0) {
-                        s.write(buffer, 0, numRead);
-                    }
-                    if (numRead != bufferSize) {
-                        break;
-                    }
-                }
-
-                return s.toByteArray();
-            } catch (IOException e) {
-                throw new RepositoryException(e);
-            } finally {
-                try {
-                    stream.close();
-                } catch (IOException ignore) {
-
-                }
-            }
-        } else {
-            return null;
-        }
-    }
-
-    private static char[] read(Reader reader) throws RepositoryException {
-        if (reader != null) {
-            try {
-                final int bufferSize = 4096;
-                CharArrayWriter w = new CharArrayWriter(bufferSize);
-
-                char[] buffer = new char[bufferSize];
-                while (true) {
-                    int numRead = reader.read(buffer);
-                    if (numRead > 0) {
-                        w.write(buffer, 0, numRead);
-                    }
-                    if (numRead != bufferSize) {
-                        break;
-                    }
-                }
-                return w.toCharArray();
-            } catch (IOException e) {
-                throw new RepositoryException(e);
-            } finally {
-                try {
-                    reader.close();
-                } catch (IOException ignore) {
-
-                }
-            }
-        } else {
-            return null;
-        }
-
-    }
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.CharArrayReader;
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+
+import javax.jcr.RepositoryException;
+
+import org.xml.sax.InputSource;
+
+/**
+ * Input source that clones existing input source. After cloning the existing
+ * input source should not be used anymore. To make more copies call
+ * {@link #cloneInputSource()} method.
+ */
+public class ClonedInputSource extends InputSource {
+    private final char[] characterArray;
+    private final byte[] byteArray;
+
+    /**
+     * Clone existing input source.
+     *
+     * @param input
+     * @throws RepositoryException
+     */
+    public ClonedInputSource(InputSource input) throws RepositoryException {
+
+        if (input == null) {
+            throw new IllegalArgumentException(
+                    "Argument 'input' may not be null.");
+        }
+
+        characterArray = read(input.getCharacterStream());
+        byteArray = read(input.getByteStream());
+
+        setEncoding(input.getEncoding());
+        setPublicId(input.getPublicId());
+        setSystemId(input.getSystemId());
+        if (characterArray != null) {
+            setCharacterStream(new CharArrayReader(characterArray));
+        }
+        if (byteArray != null) {
+            setByteStream(new ByteArrayInputStream(byteArray));
+        }
+    }
+
+    private ClonedInputSource(char[] characterArray, byte[] byteArray) {
+        super();
+        this.characterArray = characterArray;
+        this.byteArray = byteArray;
+    }
+
+    public char[] getCharacterArray() {
+        return characterArray;
+    }
+
+    public byte[] getByteArray() {
+        return byteArray;
+    }
+
+    /**
+     * Make a clone if this input source. The input source being cloned is still
+     * valid after cloning.
+     *
+     * @return input source clone.
+     */
+    public ClonedInputSource cloneInputSource() {
+
+        ClonedInputSource res = new ClonedInputSource(characterArray, byteArray);
+
+        res.setEncoding(getEncoding());
+        res.setPublicId(getPublicId());
+        res.setSystemId(getSystemId());
+
+        if (byteArray != null) {
+            res.setByteStream(new ByteArrayInputStream(byteArray));
+        }
+        if (characterArray != null) {
+            res.setCharacterStream(new CharArrayReader(characterArray));
+        }
+
+        return res;
+    }
+
+    private static byte[] read(InputStream stream) throws RepositoryException {
+        if (stream != null) {
+            try {
+                final int bufferSize = Math.min(stream.available(), 4096);
+                ByteArrayOutputStream s = new ByteArrayOutputStream(bufferSize);
+
+                byte[] buffer = new byte[bufferSize];
+                while (true) {
+                    int numRead = stream.read(buffer);
+                    if (numRead > 0) {
+                        s.write(buffer, 0, numRead);
+                    }
+                    if (numRead != bufferSize) {
+                        break;
+                    }
+                }
+
+                return s.toByteArray();
+            } catch (IOException e) {
+                throw new RepositoryException(e);
+            } finally {
+                try {
+                    stream.close();
+                } catch (IOException ignore) {
+
+                }
+            }
+        } else {
+            return null;
+        }
+    }
+
+    private static char[] read(Reader reader) throws RepositoryException {
+        if (reader != null) {
+            try {
+                final int bufferSize = 4096;
+                CharArrayWriter w = new CharArrayWriter(bufferSize);
+
+                char[] buffer = new char[bufferSize];
+                while (true) {
+                    int numRead = reader.read(buffer);
+                    if (numRead > 0) {
+                        w.write(buffer, 0, numRead);
+                    }
+                    if (numRead != bufferSize) {
+                        break;
+                    }
+                }
+                return w.toCharArray();
+            } catch (IOException e) {
+                throw new RepositoryException(e);
+            } finally {
+                try {
+                    reader.close();
+                } catch (IOException ignore) {
+
+                }
+            }
+        } else {
+            return null;
+        }
+
+    }
 }
\ No newline at end of file
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/DocViewImportHandler.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/DocViewImportHandler.java
index 0503b64..ed29a75 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/DocViewImportHandler.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/DocViewImportHandler.java
@@ -214,7 +214,7 @@ class DocViewImportHandler extends TargetImportHandler {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * See also {@link org.apache.jackrabbit.commons.xml.Exporter#exportProperties(Node)}
      * regarding special handling of multi-valued properties on export.
      */
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ImportHandler.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ImportHandler.java
index 62ef978..bde4ced 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ImportHandler.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ImportHandler.java
@@ -37,12 +37,12 @@ import org.xml.sax.helpers.DefaultHandler;
  * data in System View XML or Document View XML. Processing of the XML is
  * handled by specialized <code>ContentHandler</code>s
  * (i.e. <code>SysViewImportHandler</code> and <code>DocViewImportHandler</code>).
- * <p/>
+ * <p>
  * The actual task of importing though is delegated to the implementation of
  * the <code>{@link Importer}</code> interface.
- * <p/>
+ * <p>
  * <b>Important Note:</b>
- * <p/>
+ * <p>
  * These SAX Event Handlers expect that Namespace URI's and local names are
  * reported in the <code>start/endElement</code> events and that
  * <code>start/endPrefixMapping</code> events are reported
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ProtectedNodeImporter.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ProtectedNodeImporter.java
index 43cd85d..f2c76d7 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ProtectedNodeImporter.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/ProtectedNodeImporter.java
@@ -26,7 +26,7 @@ import java.util.List;
 /**
  * <code>ProtectedNodeImporter</code> provides means to import protected
  * <code>Node</code>s and the subtree defined below such nodes.
- * <p/>
+ * <p>
  * The import of a protected tree is started by the <code>Importer</code> by
  * calling {@link #start(NodeImpl)}. If the <code>ProtectedNodeImporter</code>
  * is able to deal with that type of protected node, it is in charge of dealing
@@ -120,7 +120,7 @@ public interface ProtectedNodeImporter extends ProtectedItemImporter {
      * whole import fails.<p/>
      * In case this importer deals with multiple levels of nodes, it is in
      * charge of maintaining the hierarchical structure (see also {#link endChildInfo()}. 
-     * <p/>
+     * <p>
      * If {@link #start(NodeImpl)} hasn't been called before, this method returns
      * silently.
      *
@@ -137,7 +137,7 @@ public interface ProtectedNodeImporter extends ProtectedItemImporter {
 
     /**
      * Informs this importer about the end of a child info.
-     * <p/>
+     * <p>
      * If {@link #start(NodeImpl)} hasn't been called before, this method returns
      * silently.
      * 
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java
index 4a563d7..e024574 100644
--- a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java
+++ b/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/WorkspaceImporter.java
@@ -86,7 +86,7 @@ public class WorkspaceImporter implements Importer {
      *
      * @param parentPath   target path where to add the imported subtree
      * @param wsp the workspace to operate on
-     * @param ntReg the node type registry
+     * @param sessionContext the session context
      * @param uuidBehavior flag that governs how incoming UUIDs are handled
      * @throws PathNotFoundException        if no node exists at
      *                                      <code>parentPath</code> or if the
@@ -115,7 +115,7 @@ public class WorkspaceImporter implements Importer {
      *
      * @param parentPath   target path where to add the imported subtree
      * @param wsp the workspace to operate on
-     * @param ntReg the node type registry
+     * @param sessionContext the session context
      * @param uuidBehavior flag that governs how incoming UUIDs are handled
      * @param config import configuration.
      * @throws PathNotFoundException        if no node exists at
diff --git a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/JackrabbitRepositoryStub.properties b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/JackrabbitRepositoryStub.properties
index b2e2a9b..8a2f821 100644
--- a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/JackrabbitRepositoryStub.properties
+++ b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/JackrabbitRepositoryStub.properties
@@ -24,6 +24,7 @@ javax.jcr.tck.readonly.pwd=
 # global test configuration
 javax.jcr.tck.testroot=/testroot
 javax.jcr.tck.nodetype=nt:unstructured
+javax.jcr.tck.nodetypetestroot=nt:unstructured
 javax.jcr.tck.nodetypenochildren=nt:address
 javax.jcr.tck.nodename1=node1
 javax.jcr.tck.nodename2=node2
@@ -159,9 +160,9 @@ javax.jcr.tck.SessionTest.testMoveItemExistsException.nodetype2=nt:folder
 javax.jcr.tck.SessionTest.testMoveItemExistsException.nodetype3=nt:folder
 
 # Test class: SessionTest
-# Test method: testSaveContstraintViolationException
+# Test method: testSaveConstraintViolationException
 # nodetype that has a property that is mandatory but not autocreated
-javax.jcr.tck.SessionTest.testSaveContstraintViolationException.nodetype2=nt:file
+javax.jcr.tck.SessionTest.testSaveConstraintViolationException.nodetype2=nt:file
 
 # Test class: SessionUUIDTest
 # node type that has a property of type PropertyType.REFERENCE
@@ -192,9 +193,9 @@ javax.jcr.tck.NodeTest.testRemoveMandatoryNode.nodetype3=nt:unstructured
 javax.jcr.tck.NodeTest.testRemoveMandatoryNode.nodename3=jcr:content
 
 # Test class: NodeTest
-# Test method: testSaveContstraintViolationException
+# Test method: testSaveConstraintViolationException
 # nodetype that has a property that is mandatory but not autocreated
-javax.jcr.tck.NodeTest.testSaveContstraintViolationException.nodetype2=nt:file
+javax.jcr.tck.NodeTest.testSaveConstraintViolationException.nodetype2=nt:file
 
 # Test class: NodeAddMixinTest
 # Test method: testAddInheritedMixin
@@ -356,11 +357,6 @@ javax.jcr.tck.NodeTypeCreationTest.testroot=/testroot
 # JAVAX.JCR.QUERY CONFIGURATION
 # ==============================================================================
 
-# Test class: SaveTest
-# Test method: testConstraintViolationException
-# Specified node type must not allow child nodes.
-javax.jcr.tck.SaveTest.testConstraintViolationException.nodetype=nt:query
-
 # Test class: XPathQueryLevel1Test
 javax.jcr.tck.XPathQueryLevel1Test.testroot=/testdata/query
 
@@ -464,6 +460,7 @@ javax.jcr.tck.OnParentVersionInitializeTest.propertyname1=test:initializeOnParen
 javax.jcr.tck.RestoreTest.testRestoreWithUUIDConflict.nodename4=test:versionOnParentVersion
 javax.jcr.tck.RestoreTest.testRestoreLabel.nodename4=test:versionOnParentVersion
 javax.jcr.tck.RestoreTest.testRestoreName.nodename4=test:versionOnParentVersion
+javax.jcr.tck.RestoreTest.testRestoreNameJcr2.nodename4=test:versionOnParentVersion
 javax.jcr.tck.RestoreTest.propertyValue1=version1
 javax.jcr.tck.RestoreTest.propertyValue2=version2
 
diff --git a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.4-elements.dtd b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.4-elements.dtd
index 3317acd..4a2d69d 100644
--- a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.4-elements.dtd
+++ b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.4-elements.dtd
@@ -105,6 +105,7 @@
 <!ELEMENT Workspaces EMPTY>
 <!ATTLIST Workspaces rootPath         CDATA #REQUIRED
                      defaultWorkspace CDATA #REQUIRED
+                     defaultLockTimeout CDATA #IMPLIED
                      configRootPath   CDATA #IMPLIED
                      maxIdleTime      CDATA #IMPLIED>
 
@@ -204,7 +205,16 @@
     The Import element configures how protected items are imported into a
     workspace.
 -->
-<!ELEMENT Import (ProtectedNodeImporter|ProtectedPropertyImporter)*>
+<!ELEMENT Import (ProtectedItemImporter|ProtectedNodeImporter|ProtectedPropertyImporter)*>
+
+<!--
+    The ProtectedItemImporter element configures an importer for protected
+    items. The class attribute specifies the FQN of the class implementing the
+    ProtectedNodeImporter interface.
+    The param(s) define implementation specific parameters.
+-->
+<!ELEMENT ProtectedItemImporter (param*)>
+<!ATTLIST ProtectedItemImporter class CDATA #REQUIRED>
 
 <!--
     The ProtectedNodeImporter element configures an importer for protected
diff --git a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.6-elements.dtd b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.6-elements.dtd
new file mode 100644
index 0000000..94db1c1
--- /dev/null
+++ b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.6-elements.dtd
@@ -0,0 +1,266 @@
+<!--
+  ~ /*
+  ~  * Licensed to the Apache Software Foundation (ASF) under one or more
+  ~  * contributor license agreements.  See the NOTICE file distributed with
+  ~  * this work for additional information regarding copyright ownership.
+  ~  * The ASF licenses this file to You under the Apache License, Version 2.0
+  ~  * (the "License"); you may not use this file except in compliance with
+  ~  * the License.  You may obtain a copy of the License at
+  ~  *
+  ~  *      http://www.apache.org/licenses/LICENSE-2.0
+  ~  *
+  ~  * Unless required by applicable law or agreed to in writing, software
+  ~  * distributed under the License is distributed on an "AS IS" BASIS,
+  ~  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~  * See the License for the specific language governing permissions and
+  ~  * limitations under the License.
+  ~  */
+  -->
+
+<!ENTITY % jackrabbit-repository-elements
+         "DataSources|Cluster|FileSystem|DataStore|Security|Workspaces|Workspace|Versioning|SearchIndex|RepositoryLockMechanism">
+
+<!--
+    The DataSources element configures the data sources of the repository.
+-->
+<!ELEMENT DataSources (DataSource*)>
+<!ELEMENT DataSource (param*)>
+<!ATTLIST DataSource name CDATA #REQUIRED>
+
+<!--
+    a virtual file system
+-->
+<!ELEMENT FileSystem (param*)>
+<!ATTLIST FileSystem class CDATA #REQUIRED>
+
+<!--
+    the Security element specifies the name (appName attribute)
+    of the JAAS configuration app-entry for this repository. 
+
+    it also specifies various security related managers to be used.
+-->
+<!ELEMENT Security (SecurityManager?, AccessManager?, LoginModule?)>
+<!ATTLIST Security appName CDATA #REQUIRED>
+
+<!--
+    the SecurityManager element configures the general security manager to be
+    used by this repository instance; the class attribute specifies the FQN of the
+    class implementing the JackrabbitSecurityManager interface
+-->
+<!ELEMENT SecurityManager (WorkspaceAccessManager?,UserManager?,UserIdClass?, param*)>
+<!ATTLIST SecurityManager class CDATA #REQUIRED
+                          workspaceName CDATA #IMPLIED>
+
+<!--
+    the AccessManager element configures the access manager to be used by
+    this repository instance; the class attribute specifies the FQN of the
+    class implementing the AccessManager interface
+-->
+<!ELEMENT AccessManager (param*)>
+<!ATTLIST AccessManager class CDATA #REQUIRED>
+
+<!--
+    generic parameter (name/value pair)
+    this element can also have custom objects 
+-->
+<!ELEMENT param (param*)>
+<!ATTLIST param name  CDATA #REQUIRED
+                value CDATA #REQUIRED>
+
+<!--
+    the LoginModule element optionally specifies a JAAS login module to
+    authenticate users. This feature allows the use of Jackrabbit in a
+    non-JAAS environment.
+-->
+<!ELEMENT LoginModule (param*)>
+<!ATTLIST LoginModule class CDATA #REQUIRED>
+
+<!--
+    the WorkspaceAccessManager element optionally configures the manager
+    to be used by this repository instance to determine if access to a specific
+    workspace is granted for a specific subject;
+    the class attribute specifies the FQN of the class implementing the
+    WorkspaceAccessManager interface
+-->
+<!ELEMENT WorkspaceAccessManager EMPTY>
+<!ATTLIST WorkspaceAccessManager class CDATA #REQUIRED>
+
+<!--
+    the Workspaces element specifies the physical workspaces root directory
+    (rootPath attribute), the name of the default workspace (defaultWorkspace 
+    attribute), the (optional) maximum amount of time in seconds before an idle 
+    workspace is automatically shutdown (maxIdleTime attribute) and the 
+    (optional) workspace configuration root directory within the virtual 
+    repository file system (configRootPath attribute).
+
+    individual workspaces are configured through individual workspace.xml files 
+    located in a subfolder each of either
+
+    a) the physical workspaces root directory
+
+    or, if configRootPath had been specified,
+
+    b) the configuration root directory within the virtual repository file 
+    system.
+-->
+<!ELEMENT Workspaces EMPTY>
+<!ATTLIST Workspaces rootPath         CDATA #REQUIRED
+                     defaultWorkspace CDATA #REQUIRED
+                     defaultLockTimeout CDATA #IMPLIED
+                     configRootPath   CDATA #IMPLIED
+                     maxIdleTime      CDATA #IMPLIED>
+
+<!--
+    the Workspace element serves as a workspace configuration template;
+    it is used to create the initial workspace if there's no workspace yet
+    and for creating additional workspaces through the api
+-->
+<!ELEMENT Workspace (FileSystem,PersistenceManager,SearchIndex?,ISMLocking?,WorkspaceSecurity?,Import?)>
+<!ATTLIST Workspace name CDATA #REQUIRED>
+
+<!--
+    the PersistenceManager element configures the persistence manager
+    to be used for the workspace; the class attribute specifies the
+    FQN of the class implementing the PersistenceManager interface
+-->
+<!ELEMENT PersistenceManager (param*)>
+<!ATTLIST PersistenceManager class CDATA #REQUIRED>
+
+<!--
+    the SearchIndex element specifies the locaction of the search index
+    (used by the QueryHandler); the class attribute specifies the
+    FQN of the class implementing the QueryHandler interface.
+-->
+<!ELEMENT SearchIndex (param*,FileSystem?)>
+<!ATTLIST SearchIndex class CDATA #REQUIRED>
+
+
+<!--
+    the WorkspaceSecurity element specifies the workspace specific security
+    configuration.
+-->
+<!ELEMENT WorkspaceSecurity (AccessControlProvider?)>
+
+<!--
+    the AccessControlProvider element defines a class attribute specifying the
+    FQN of the class implementing the AccessControlProvider interface.
+    The param(s) define implementation specific parameters.
+-->
+<!ELEMENT AccessControlProvider (param*)>
+<!ATTLIST AccessControlProvider class CDATA #REQUIRED>
+
+<!--
+    the Versioning element configures the persistence manager
+    to be used for persisting version state
+-->
+<!ELEMENT Versioning (FileSystem, PersistenceManager, ISMLocking?)>
+<!ATTLIST Versioning rootPath CDATA #REQUIRED>
+
+<!--
+    the Cluster element configures the optional participation of this
+    repository in a clustered environment. a literal id may be
+    specified that uniquely identifies this node in a cluster, as well
+    as the delay in milliseconds before changes to the journal are
+    automatically detected. The stopDelay in milliseconds controls how long
+    the repository waits for the journal thread to terminate. The stop delay
+    is implementation specific if no value is specified in the configuration.
+-->
+<!ELEMENT Cluster (Journal)>
+<!ATTLIST Cluster id        CDATA #IMPLIED
+                  syncDelay CDATA #IMPLIED
+                  stopDelay CDATA #IMPLIED>
+
+<!--
+    the Journal element configures the journal used in clustering; the
+    class attribute specifies the FQN of the class implementing the
+    Journal interface.
+-->
+<!ELEMENT Journal (param*)>
+<!ATTLIST Journal class CDATA #REQUIRED>
+
+<!--
+    the ISMLocking element configures the locking implementation
+    to be used for the workspace and version storage; the class
+    attribute specifies the FQN of the class implementing the
+    ISMLocking interface.
+-->
+<!ELEMENT ISMLocking (param*)>
+<!ATTLIST ISMLocking class CDATA #REQUIRED>
+
+<!--
+    the RepositoryLockMechanism element configures the mechanism
+    that is used to ensure only one process writes to the 
+    backend (file system or database) at any time; the class
+    attribute specifies the FQN of the class implementing the
+    RepositoryLockMechanism interface.
+-->
+<!ELEMENT RepositoryLockMechanism (param*)>
+<!ATTLIST RepositoryLockMechanism class CDATA #REQUIRED>
+
+<!--
+    the DataStore element configures the data store
+    to be used for the workspace; the class attribute specifies the
+    FQN of the class implementing the DataStore interface
+-->
+<!ELEMENT DataStore (param*)>
+<!ATTLIST DataStore class CDATA #REQUIRED>
+
+<!--
+    The Import element configures how protected items are imported into a
+    workspace.
+-->
+<!ELEMENT Import (ProtectedItemImporter|ProtectedNodeImporter|ProtectedPropertyImporter)*>
+
+<!--
+    The ProtectedItemImporter element configures an importer for protected
+    items. The class attribute specifies the FQN of the class implementing the
+    ProtectedNodeImporter interface.
+    The param(s) define implementation specific parameters.
+-->
+<!ELEMENT ProtectedItemImporter (param*)>
+<!ATTLIST ProtectedItemImporter class CDATA #REQUIRED>
+
+<!--
+    The ProtectedNodeImporter element configures an importer for protected
+    nodes. The class attribute specifies the FQN of the class implementing the
+    ProtectedNodeImporter interface.
+    The param(s) define implementation specific parameters.
+-->
+<!ELEMENT ProtectedNodeImporter (param*)>
+<!ATTLIST ProtectedNodeImporter class CDATA #REQUIRED>
+
+<!--
+    The ProtectedPropertyImporter element configures an importer for protected
+    properties. The class attribute specifies the FQN of the class implementing
+    the ProtectedPropertyImporter interface.
+    The param(s) define implementation specific parameters.
+-->
+<!ELEMENT ProtectedPropertyImporter (param*)>
+<!ATTLIST ProtectedPropertyImporter class CDATA #REQUIRED>
+
+<!--
+    The UserManager element configures the user manager implementation that is
+    used in Jackrabbit. The class attribute specifies the FQN of the class
+    implementing the UserManager interface.
+    The param(s) define implementation specific parameters.
+-->
+<!ELEMENT UserManager (param*,AuthorizableAction*)>
+<!ATTLIST UserManager class CDATA #REQUIRED>
+
+<!--
+   The optional AuthorizableAction element(s) configure additional custom
+   actions to be executed upon authorizable creation and removal. The 'class'
+   attribute specifies the FQN of a class implementing AuthorizableAction interface.
+   The parameter(s) define the implementation specific configuration.
+-->
+<!ELEMENT AuthorizableAction (param*)>
+<!ATTLIST AuthorizableAction class CDATA #REQUIRED>
+
+
+<!--
+    The UserIdClass element specifies the class of principals used to retrieve
+    the userID out of a Subject. The class attribute specifies the FQN of a
+    class implementing the java.security.Principal interface.
+-->
+<!ELEMENT UserIdClass EMPTY>
+<!ATTLIST UserIdClass class CDATA #REQUIRED>
diff --git a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.6.dtd b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.6.dtd
new file mode 100644
index 0000000..8b948fd
--- /dev/null
+++ b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/config/repository-2.6.dtd
@@ -0,0 +1,64 @@
+<!--
+  ~ /*
+  ~  * Licensed to the Apache Software Foundation (ASF) under one or more
+  ~  * contributor license agreements.  See the NOTICE file distributed with
+  ~  * this work for additional information regarding copyright ownership.
+  ~  * The ASF licenses this file to You under the Apache License, Version 2.0
+  ~  * (the "License"); you may not use this file except in compliance with
+  ~  * the License.  You may obtain a copy of the License at
+  ~  *
+  ~  *      http://www.apache.org/licenses/LICENSE-2.0
+  ~  *
+  ~  * Unless required by applicable law or agreed to in writing, software
+  ~  * distributed under the License is distributed on an "AS IS" BASIS,
+  ~  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~  * See the License for the specific language governing permissions and
+  ~  * limitations under the License.
+  ~  */
+  -->
+
+<!ENTITY % repository-elements
+         PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 2.6 Elements//EN"
+         "http://jackrabbit.apache.org/dtd/repository-2.6-elements.dtd">
+%repository-elements;
+
+<!--
+    the Repository element configures a repository instance; individual 
+    workspaces of the repository are configured through separate configuration 
+    files called workspace.xml which are located in a subfolder of the 
+    workspaces root directory (see Workspaces element).
+
+    it consists of
+
+      - an optional Cluster element that is used for configuring a
+        clustering node that synchronizes changes made in a cluster
+       
+      - a FileSystem element (the virtual file system
+        used by the repository to persist global state such as
+        registered namespaces, custom node types, etc.
+        
+      - an optional DataStore element to configure the component
+        to use for storing large binary objects
+
+      - a Security element that specifies the name of the app-entry
+        in the JAAS config and the access manager
+
+      - a Workspaces element that specifies the location of the 
+        workspaces root directory, the name of the default workspace,
+        the maximum idle time before a workspace is automatically
+        shutdown (optional) and the workspace configuration root directory
+        within the virtual repository file system (optional)
+
+      - a Workspace element that is used as a workspace configuration
+        template; it is used to create the initial workspace if there's
+        no workspace yet and for creating additional workspaces through
+        the API
+
+      - a Versioning element that is used for configuring
+        versioning-related settings
+
+      - an optional SearchIndex element that is used for configuring Indexing-related
+        settings on the /jcr:system tree.
+
+-->
+<!ELEMENT Repository (%jackrabbit-repository-elements;)*> 
diff --git a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/azure.properties b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/azure.properties
deleted file mode 100644
index 7242350..0000000
--- a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/azure.properties
+++ /dev/null
@@ -1,17 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one or more
-#  contributor license agreements.  See the NOTICE file distributed with
-#  this work for additional information regarding copyright ownership.
-#  The ASF licenses this file to You under the Apache License, Version 2.0
-#  (the "License"); you may not use this file except in compliance with
-#  the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
-createTable=CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY, LENGTH BIGINT, LAST_MODIFIED BIGINT, DATA IMAGE)
diff --git a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/db2.properties b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/db2.properties
deleted file mode 100644
index d146d0e..0000000
--- a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/db2.properties
+++ /dev/null
@@ -1,17 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one or more
-#  contributor license agreements.  See the NOTICE file distributed with
-#  this work for additional information regarding copyright ownership.
-#  The ASF licenses this file to You under the Apache License, Version 2.0
-#  (the "License"); you may not use this file except in compliance with
-#  the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-driver=COM.ibm.db2.jdbc.net.DB2Driver
-createTable=CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY NOT NULL, LENGTH BIGINT, LAST_MODIFIED BIGINT, DATA BLOB(1000M)) 
diff --git a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/derby.properties b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/derby.properties
deleted file mode 100644
index 3845f84..0000000
--- a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/derby.properties
+++ /dev/null
@@ -1,17 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one or more
-#  contributor license agreements.  See the NOTICE file distributed with
-#  this work for additional information regarding copyright ownership.
-#  The ASF licenses this file to You under the Apache License, Version 2.0
-#  (the "License"); you may not use this file except in compliance with
-#  the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-# Tested with Apache Derby 10.3.1.4 on Windows XP (2007-12-11)
-driver=org.apache.derby.jdbc.EmbeddedDriver
\ No newline at end of file
diff --git a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/h2.properties b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/h2.properties
deleted file mode 100644
index 5c0e1f0..0000000
--- a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/h2.properties
+++ /dev/null
@@ -1,18 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one or more
-#  contributor license agreements.  See the NOTICE file distributed with
-#  this work for additional information regarding copyright ownership.
-#  The ASF licenses this file to You under the Apache License, Version 2.0
-#  (the "License"); you may not use this file except in compliance with
-#  the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-# Tested with H2 1.0.63 on Windows XP (2007-12-11)
-driver=org.h2.Driver
-storeStream=-1
\ No newline at end of file
diff --git a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/ingres.properties b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/ingres.properties
deleted file mode 100644
index 29ca2c2..0000000
--- a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/ingres.properties
+++ /dev/null
@@ -1,17 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one or more
-#  contributor license agreements.  See the NOTICE file distributed with
-#  this work for additional information regarding copyright ownership.
-#  The ASF licenses this file to You under the Apache License, Version 2.0
-#  (the "License"); you may not use this file except in compliance with
-#  the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-driver=com.ingres.jdbc.IngresDriver
-createTable=CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY NOT NULL, LENGTH BIGINT, LAST_MODIFIED BIGINT, DATA LONG BYTE) 
diff --git a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/mssql.properties b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/mssql.properties
deleted file mode 100644
index 7242350..0000000
--- a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/mssql.properties
+++ /dev/null
@@ -1,17 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one or more
-#  contributor license agreements.  See the NOTICE file distributed with
-#  this work for additional information regarding copyright ownership.
-#  The ASF licenses this file to You under the Apache License, Version 2.0
-#  (the "License"); you may not use this file except in compliance with
-#  the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
-createTable=CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY, LENGTH BIGINT, LAST_MODIFIED BIGINT, DATA IMAGE)
diff --git a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/mysql.properties b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/mysql.properties
deleted file mode 100644
index f6195a0..0000000
--- a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/mysql.properties
+++ /dev/null
@@ -1,19 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one or more
-#  contributor license agreements.  See the NOTICE file distributed with
-#  this work for additional information regarding copyright ownership.
-#  The ASF licenses this file to You under the Apache License, Version 2.0
-#  (the "License"); you may not use this file except in compliance with
-#  the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-# Tested with MySQL 5.0.27-community-nt on Windows XP (2007-12-11)
-# currently, the objects must fit in memory
-driver=com.mysql.jdbc.Driver
-createTable=CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY, LENGTH BIGINT, LAST_MODIFIED BIGINT, DATA BLOB(2147483647))
diff --git a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/oracle.properties b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/oracle.properties
deleted file mode 100644
index 94dd2ac..0000000
--- a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/oracle.properties
+++ /dev/null
@@ -1,18 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one or more
-#  contributor license agreements.  See the NOTICE file distributed with
-#  this work for additional information regarding copyright ownership.
-#  The ASF licenses this file to You under the Apache License, Version 2.0
-#  (the "License"); you may not use this file except in compliance with
-#  the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-# Tested with Oracle Database 10g Release 10.2.0.1.0 on Windows XP (2008-04-29)
-driver=oracle.jdbc.OracleDriver
-createTable=CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY, LENGTH NUMBER, LAST_MODIFIED NUMBER, DATA BLOB)
diff --git a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/postgresql.properties b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/postgresql.properties
deleted file mode 100644
index c1b6b15..0000000
--- a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/postgresql.properties
+++ /dev/null
@@ -1,20 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one or more
-#  contributor license agreements.  See the NOTICE file distributed with
-#  this work for additional information regarding copyright ownership.
-#  The ASF licenses this file to You under the Apache License, Version 2.0
-#  (the "License"); you may not use this file except in compliance with
-#  the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-# Tested with PostgreSQL 8.2.4 on Windows XP (2007-12-11)
-# currently, the objects must fit in memory
-driver=org.postgresql.Driver
-table=datastore
-createTable=CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY, LENGTH BIGINT, LAST_MODIFIED BIGINT, DATA BYTEA)
diff --git a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/sqlserver.properties b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/sqlserver.properties
deleted file mode 100644
index eac9a6f..0000000
--- a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/data/db/sqlserver.properties
+++ /dev/null
@@ -1,18 +0,0 @@
-#  Licensed to the Apache Software Foundation (ASF) under one or more
-#  contributor license agreements.  See the NOTICE file distributed with
-#  this work for additional information regarding copyright ownership.
-#  The ASF licenses this file to You under the Apache License, Version 2.0
-#  (the "License"); you may not use this file except in compliance with
-#  the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-
-# Tested with Microsoft SQL Server 2005 4 on Windows XP (2007-12-11)
-driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
-createTable=CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY, LENGTH BIGINT, LAST_MODIFIED BIGINT, DATA IMAGE)
diff --git a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.cnd b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.cnd
index 43179db..dfd785d 100644
--- a/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.cnd
+++ b/jackrabbit-core/src/main/resources/org/apache/jackrabbit/core/nodetype/builtin_nodetypes.cnd
@@ -603,7 +603,7 @@
   - rep:impersonators (STRING) protected multiple
 
 [rep:User] > rep:Authorizable, rep:Impersonatable
-  - rep:password (STRING) protected mandatory
+  - rep:password (STRING) protected
   - rep:disabled (STRING) protected
 
 [rep:Group] > rep:Authorizable
@@ -627,3 +627,12 @@
   mixin
   - rep:hold (UNDEFINED) protected  multiple IGNORE
   - rep:retentionPolicy (UNDEFINED) protected IGNORE
+
+// -----------------------------------------------------------------------------
+// Token Management (backported from oak)
+// -----------------------------------------------------------------------------
+[rep:Token] > mix:referenceable
+  - rep:token.key (STRING) protected mandatory
+  - rep:token.exp (DATE) protected mandatory
+  - * (UNDEFINED) protected
+  - * (UNDEFINED) multiple protected
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/JackrabbitObservationManagerTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/JackrabbitObservationManagerTest.java
new file mode 100644
index 0000000..bb5c01c
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/JackrabbitObservationManagerTest.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.api;
+
+import static javax.jcr.observation.Event.NODE_ADDED;
+
+import java.util.concurrent.ExecutionException;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.observation.Event;
+
+import org.apache.jackrabbit.api.observation.JackrabbitEventFilter;
+import org.apache.jackrabbit.api.observation.JackrabbitObservationManager;
+import org.apache.jackrabbit.test.api.observation.AbstractObservationTest;
+import org.apache.jackrabbit.test.api.observation.EventResult;
+
+public class JackrabbitObservationManagerTest extends AbstractObservationTest {
+
+    public void testImplementsJackrabbitObservationManager() throws RepositoryException {
+        assertTrue(obsMgr instanceof JackrabbitObservationManager);
+    }
+
+    public void testDisjunctPaths() throws ExecutionException, InterruptedException, RepositoryException {
+        JackrabbitObservationManager oManager = (JackrabbitObservationManager) obsMgr;
+        EventResult listener = new EventResult(log);
+        JackrabbitEventFilter filter = new JackrabbitEventFilter()
+                .setAdditionalPaths('/' + testPath + "/a", '/' + testPath + "/x")
+                .setEventTypes(NODE_ADDED);
+        oManager.addEventListener(listener, filter);
+        try {
+            Node b = testRootNode.addNode("a").addNode("b");
+            b.addNode("c");
+            Node y = testRootNode.addNode("x").addNode("y");
+            y.addNode("z");
+            testRootNode.getSession().save();
+
+            Event[] added = listener.getEvents(DEFAULT_WAIT_TIMEOUT);
+            checkNodeAdded(added, new String[] {"a/b", "x/y"}, null);
+        } finally {
+            oManager.removeEventListener(listener);
+        }
+    }
+
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/TestAll.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/TestAll.java
index d5785a4..3cf1e6e 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/TestAll.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/TestAll.java
@@ -36,6 +36,7 @@ public class TestAll extends AbstractJCRTest {
         TestSuite suite = new TestSuite("api tests");
 
         suite.addTestSuite(JackrabbitNodeTest.class);
+        suite.addTestSuite(JackrabbitObservationManagerTest.class);
 
         return suite;
     }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/UserManagerCreateUserTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/UserManagerCreateUserTest.java
index 5ef2b1d..684d41a 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/UserManagerCreateUserTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/UserManagerCreateUserTest.java
@@ -16,14 +16,14 @@
  */
 package org.apache.jackrabbit.api.security.user;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.jackrabbit.test.NotExecutableException;
-
-import javax.jcr.RepositoryException;
 import java.security.Principal;
 import java.util.ArrayList;
 import java.util.List;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.test.NotExecutableException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * <code>UserManagerCreateGroupTest</code>...
@@ -151,16 +151,15 @@ public class UserManagerCreateUserTest extends AbstractUserTest {
         }
     }
 
-    public void testCreateUserWithNullPassword() throws RepositoryException {
-        try {
-            Principal p = getTestPrincipal();
-            User user = createUser(p.getName(), null);
-            createdUsers.add(user);
-
-            fail("A User cannot be built with 'null' password");
-        } catch (Exception e) {
-            // ok
-        }
+    /**
+     * Test for changed behavior that allows creating of users with 'null' password.
+     *
+     * @since Jackrabbit 2.7
+     */
+    public void testCreateUserWithNullPassword() throws RepositoryException, NotExecutableException {
+        Principal p = getTestPrincipal();
+        User user = createUser(p.getName(), null);
+        createdUsers.add(user);
     }
 
     public void testCreateUserWithEmptyPassword() throws RepositoryException, NotExecutableException {
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/UserManagerTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/UserManagerTest.java
index fdc5446..66eed5b 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/UserManagerTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/UserManagerTest.java
@@ -70,4 +70,60 @@ public class UserManagerTest extends AbstractUserTest {
             throw new NotExecutableException();
         }
     }
+
+
+    public void testGetAuthorizableByIdAndType() throws NotExecutableException, RepositoryException {
+        for (Principal principal : getPrincipalSetFromSession(superuser)) {
+            Principal p = principal;
+            Authorizable a = userMgr.getAuthorizable(p);
+            if (a != null) {
+                Authorizable authorizable = userMgr.getAuthorizable(a.getID(), a.getClass());
+                assertEquals("Equal ID expected", a.getID(), authorizable.getID());
+
+                authorizable = userMgr.getAuthorizable(a.getID(), Authorizable.class);
+                assertEquals("Equal ID expected", a.getID(), authorizable.getID());
+            }
+        }
+    }
+
+    public void testGetAuthorizableByIdAndWrongType() throws NotExecutableException, RepositoryException {
+        for (Principal principal : getPrincipalSetFromSession(superuser)) {
+            Principal p = principal;
+            Authorizable auth = userMgr.getAuthorizable(p);
+            if (auth != null) {
+                Class<? extends Authorizable> otherType = auth.isGroup() ? User.class : Group.class;
+                try {
+                    userMgr.getAuthorizable(auth.getID(), otherType);
+                    fail("Wrong Authorizable type is not detected.");
+                } catch (AuthorizableTypeException e) {
+                    // success
+                }
+            }
+        }
+    }
+
+    public void testGetNonExistingAuthorizableByIdAndType() throws NotExecutableException, RepositoryException {
+        Authorizable auth = userMgr.getAuthorizable("nonExistingAuthorizable", User.class);
+        assertNull(auth);
+
+        auth = userMgr.getAuthorizable("nonExistingAuthorizable", Authorizable.class);
+        assertNull(auth);
+    }
+
+    public void testGetAuthorizableByNullType() throws Exception {
+        String uid = superuser.getUserID();
+        Authorizable auth = userMgr.getAuthorizable(uid);
+        if (auth != null) {
+            try {
+                userMgr.getAuthorizable(uid, null);
+                fail("Null Authorizable type is not detected.");
+            } catch (AuthorizableTypeException e) {
+                // success
+            }
+        }
+    }
+
+    public void testGetNonExistingAuthorizableByNullType() throws Exception {
+        assertNull(userMgr.getAuthorizable("nonExistingAuthorizable", null));
+    }
 }
\ No newline at end of file
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/AddMoveTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/AddMoveTest.java
new file mode 100644
index 0000000..db36725
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/AddMoveTest.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.core.persistence.check.ConsistencyReport;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.jackrabbit.test.NotExecutableException;
+
+public class AddMoveTest extends AbstractJCRTest {
+
+    private String folder1Path;
+    private String folder2Path;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        Node folder1 = testRootNode.addNode("folder1");
+        folder1Path = folder1.getPath();
+        Node folder2 = testRootNode.addNode("folder2");
+        folder2Path = folder2.getPath();
+        folder1.addNode("node1");
+        testRootNode.getSession().save();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testAddMove() throws RepositoryException, NotExecutableException {
+        Session session1 = getHelper().getReadWriteSession();
+        Session session2 = getHelper().getReadWriteSession();
+
+        session1.getNode(folder1Path).addNode("node2");
+        session2.move(folder1Path + "/node1", folder2Path + "/node1");
+        session2.save();
+        Node node = session1.getNode(folder2Path + "/node1");
+        node.setProperty("foo", "bar");
+        session1.save();
+
+        ConsistencyReport consistencyReport = TestHelper.checkConsistency(testRootNode.getSession(), false, null);
+        //for (ReportItem item : consistencyReport.getItems()) {
+        //    System.out.println(item.getMessage());
+        //}
+        assertTrue(consistencyReport.getItems().size() == 0);
+    }
+
+    /**
+     * Add a top level node and rename it. Exposes a bug in the {@code CachingHierarchyManager},
+     * reported in JCR-3379.
+     */
+    public void testTopLevelAddMove() throws Exception {
+        Session session = getHelper().getReadWriteSession();
+        session.getRootNode().addNode("foo");
+        session.save();
+        Node fooNode = session.getNode("/foo");
+        assertEquals("/foo", fooNode.getPath());
+        session.move("/foo", "/bar");
+        Node barNode = session.getNode("/bar");
+        assertEquals("/bar", barNode.getPath());
+    }
+
+    /**
+     * Add a top level node and remove it. Exposes a bug in the {@code CachingHierarchyManager},
+     * reported in JCR-3368.
+     */
+    public void testTopLevelAddRemove() throws Exception {
+        Session session = getHelper().getReadWriteSession();
+        session.getRootNode().addNode("foo").addNode("bar");
+        session.save();
+        session.getNode("/foo").remove();
+        assertFalse(session.getRootNode().hasNode("foo/bar"));
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentAddMoveRemoveTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentAddMoveRemoveTest.java
new file mode 100644
index 0000000..ff6b4b4
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentAddMoveRemoveTest.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core;
+
+import java.util.Random;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.core.integration.random.operation.Operation;
+import org.apache.jackrabbit.core.persistence.check.ConsistencyReport;
+
+/**
+ * <code>ConcurrentAddMoveRemoveTest</code> performs a test with 5 threads which
+ * concurrently add, (workspace) move and remove nodes. This test is intended to 
+ * make sure these concurrent actions don't lead to inconsistencies in the database.
+ * See also: JCR-3292.
+ */
+public class ConcurrentAddMoveRemoveTest extends AbstractConcurrencyTest {
+
+    private static final int RUN_NUM_SECONDS = 20;
+
+    private long end;
+
+    private String folder1Path;
+    private String folder2Path;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        end = System.currentTimeMillis() + RUN_NUM_SECONDS * 1000;
+        folder1Path = testRootNode.addNode("folder1").getPath();
+        folder2Path = testRootNode.addNode("folder2").getPath();
+        testRootNode.getSession().save();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        ConsistencyReport consistencyReport = TestHelper.checkConsistency(testRootNode.getSession(), false, null);
+        //for (ReportItem item : consistencyReport.getItems()) {
+        //    System.out.println(item.getMessage());
+        //}
+        assertTrue(consistencyReport.getItems().size() == 0);
+        super.tearDown();
+    }
+
+    public void testRandomOperations() throws RepositoryException {
+        runTask(new AddMoveRemoveTask(end, folder1Path, folder2Path), 5, testRootNode.getPath());
+    }
+
+    public static class AddMoveRemoveTask implements Task {
+
+        private final long end;
+        private final String folder1Path;
+        private final String folder2Path;
+
+        private final Random random = new Random();
+
+        public AddMoveRemoveTask(long end, String folder1Path, String folder2Path) {
+            this.end = end;
+            this.folder1Path = folder1Path;
+            this.folder2Path = folder2Path;
+        }
+
+        public void execute(final Session session, final Node test) throws RepositoryException {
+            while (end > System.currentTimeMillis()) {
+                try {
+                    getRandomOperation(session).execute();
+                } catch (RepositoryException e) {
+                    // RepositoryExceptions are expected during concurrent actions.
+                    session.refresh(false);
+                } catch (Exception e) {
+                    // only a RepositoryException is allowed from a Task
+                    throw new RepositoryException("Failure during concurrent execution", e);
+                }
+            }
+        }
+
+        private Operation getRandomOperation(Session session) throws RepositoryException {
+            switch (random.nextInt(3)) {
+            case 0:
+                return new Add(session, getRandomParent());
+            case 1: {
+                String folderPath = getRandomParent();
+                return new WorkspaceMove(session, getRandomChild(session, folderPath),
+                        folderPath.equals(folder1Path) ? folder2Path : folder1Path);
+            }
+            default: {
+                String folderPath = getRandomParent();
+                return new Remove(session, getRandomChild(session, folderPath));
+            }
+            }
+        }
+
+        private String getRandomChild(Session session, String parentPath) throws RepositoryException {
+            Node parent = session.getNode(parentPath);
+            NodeIterator nodes = parent.getNodes();
+            int size = (int) nodes.getSize();
+            if (size > 0) {
+                int i = 1;
+                int offset = random.nextInt(size);
+                while (nodes.hasNext()) {
+                    if (i == offset) {
+                        return nodes.nextNode().getPath();
+                    }
+                    nodes.nextNode();
+                    i++;
+                }
+            }
+            return null;
+        }
+
+        private String getRandomParent() {
+            switch (random.nextInt(2)) {
+            case 0:
+                return folder1Path;
+            default:
+                return folder2Path;
+            }
+        }
+
+        private class Add extends Operation {
+
+            private final String name;
+
+            public Add(Session s, String folderPath) {
+                super(s, folderPath);
+                this.name = getRandomText(4);
+            }
+
+            @Override
+            public NodeIterator execute() throws Exception {
+                getNode().addNode(name);
+                getSession().save();
+                return null;
+            }
+        }
+
+        private class Remove extends Operation {
+
+            public Remove(Session s, String path) {
+                super(s, path);
+            }
+
+            @Override
+            public NodeIterator execute() throws Exception {
+                if (getPath() != null) {
+                    getNode().remove();
+                    getSession().save();
+                }
+                return null;
+            }
+
+        }
+
+        private class WorkspaceMove extends Operation {
+
+            private final String folderPath;
+
+            public WorkspaceMove(Session s, String path, String folderPath) {
+                super(s, path);
+                this.folderPath = folderPath;
+            }
+
+            @Override
+            public NodeIterator execute() throws Exception {
+                if (getPath() != null) {
+                    String destination = folderPath + "/" + getRandomText(4);
+                    getSession().getWorkspace().move(getPath(), destination);
+                }
+                return null;
+            }
+
+        }
+    }
+
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentCyclicMoveTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentCyclicMoveTest.java
new file mode 100644
index 0000000..1ad3bb4
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentCyclicMoveTest.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core;
+
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.test.AbstractJCRTest;
+
+public class ConcurrentCyclicMoveTest extends AbstractJCRTest {
+
+    private String testRootPath;
+
+    public void testConcurrentSessionMove() throws RepositoryException {
+
+        testRootPath = testRootNode.getPath();
+        Node aa = testRootNode.addNode("a").addNode("aa");
+        Node b = testRootNode.addNode("b");
+        testRootNode.getSession().save();
+
+        String aaId = aa.getIdentifier();
+        String bId= b.getIdentifier();
+        
+        Session session1 = getHelper().getReadWriteSession();
+        Session session2 = getHelper().getReadWriteSession();
+
+        // results in /b/a/aa
+        session1.move(testRootPath + "/a", testRootPath + "/b/a");
+        assertEquals(testRootPath + "/b/a/aa", session1.getNodeByIdentifier(aaId).getPath());
+
+        // results in a/aa/b
+        session2.move(testRootPath + "/b", testRootPath + "/a/aa/b");
+        assertEquals(testRootPath + "/a/aa/b", session2.getNodeByIdentifier(bId).getPath());
+
+        session1.save();
+
+        try {
+            session2.getNodeByIdentifier(bId).getPath();
+            fail("It should not be possible to access a cyclic path");
+        } catch (InvalidItemStateException expected) {
+        }
+
+        try {
+            session2.save();
+            fail("Save should have failed. Possible cyclic persistent path created.");
+        } catch (InvalidItemStateException expected) {
+        }
+    }
+
+
+    public void testConcurrentWorkspaceMove() throws RepositoryException {
+
+        testRootPath = testRootNode.getPath();
+        testRootNode.addNode("b");
+        Node aa = testRootNode.addNode("a").addNode("aa");
+        testRootNode.getSession().save();
+
+        String aaId = aa.getIdentifier();
+        
+        Session session1 = getHelper().getReadWriteSession();
+        Session session2 = getHelper().getReadWriteSession();
+
+        // results in /b/a/aa
+        session1.getWorkspace().move(testRootPath + "/a", testRootPath + "/b/a");
+        assertEquals(testRootPath + "/b/a/aa", session1.getNodeByIdentifier(aaId).getPath());
+        
+        // try to move b into a/aa (should fail as the above move is persisted
+        try {
+            session2.getWorkspace().move(testRootPath + "/b", testRootPath + "/a/aa/b");
+            fail("Workspace.move() should not have succeeded. Possible cyclic path created.");
+        } catch (PathNotFoundException e) {
+            // expected.
+        }
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentImportTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentImportTest.java
index 7ddbf95..57f818f 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentImportTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentImportTest.java
@@ -215,7 +215,7 @@ public class ConcurrentImportTest extends AbstractConcurrencyTest {
 
     private void checkConsistency() throws RepositoryException {
         try {
-            ConsistencyReport rep = TestHelper.checkConsistency(testRootNode.getSession(), false);
+            ConsistencyReport rep = TestHelper.checkConsistency(testRootNode.getSession(), false, null);
             assertEquals("Found broken nodes in repository: " + rep, 0, rep.getItems().size());
         } catch (NotExecutableException ex) {
             // ignore
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentNodeModificationTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentNodeModificationTest.java
index dc402f7..887fa09 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentNodeModificationTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentNodeModificationTest.java
@@ -28,7 +28,7 @@ import javax.jcr.Value;
 /**
  * Performs a test with n sessions concurrently performing non-conflicting
  * modifications on the <i>same</i> node.
- * <p/>
+ * <p>
  * See http://issues.apache.org/jira/browse/JCR-584.
  */
 public class ConcurrentNodeModificationTest extends AbstractJCRTest {
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentWorkspaceCopyTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentWorkspaceCopyTest.java
index 2b7ce51..a469399 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentWorkspaceCopyTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConcurrentWorkspaceCopyTest.java
@@ -1,119 +1,119 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core;
-
-import org.apache.jackrabbit.test.AbstractJCRTest;
-
-import javax.jcr.NodeIterator;
-import javax.jcr.Session;
-import javax.jcr.RepositoryException;
-import java.util.Random;
-
-
-public class ConcurrentWorkspaceCopyTest extends AbstractJCRTest {
-
-    private static final int NUM_ITERATIONS = 40;
-    private static final int NUM_SESSIONS = 2;
-
-    static final String TARGET_NAME = "copy of src";
-    String sourcePath;
-    String destParentPath;
-    String destPath;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        // create a parent node where allowSameNameSiblings is set to false
-        destParentPath =
-                testRootNode.addNode("destParent",
-                        "nt:folder").getPath();
-        destPath = destParentPath + "/" + TARGET_NAME;
-        // create a source node
-        sourcePath = testRootNode.addNode("src", "nt:folder").getPath();
-
-        testRootNode.getSession().save();
-    }
-
-    public void testConcurrentCopy() throws Exception {
-        for (int n = 0; n < NUM_ITERATIONS; n++) {
-            // cleanup
-            while (superuser.nodeExists(destPath)) {
-                superuser.getNode(destPath).remove();
-                superuser.save();
-            }
-
-            Thread[] threads = new Thread[NUM_SESSIONS];
-            for (int i = 0; i < threads.length; i++) {
-                // create new session
-                Session session = getHelper().getSuperuserSession();
-                String id = "session#" + i;
-                TestSession ts = new TestSession(id, session);
-                Thread t = new Thread(ts);
-                t.setName(id);
-                t.start();
-                threads[i] = t;
-            }
-            for (int i = 0; i < threads.length; i++) {
-                threads[i].join();
-            }
-
-            NodeIterator results = superuser.getNode(destParentPath).getNodes(TARGET_NAME);
-
-            assertEquals(1, results.getSize());
-        }
-    }
-
-
-    // -------------------------------------------------------< inner classes >
-    class TestSession implements Runnable {
-
-        Session session;
-        String identity;
-        Random r;
-
-        TestSession(String identity, Session s) {
-            session = s;
-            this.identity = identity;
-            r = new Random();
-        }
-
-        private void randomSleep() {
-            long l = r.nextInt(90) + 20;
-            try {
-                Thread.sleep(l);
-            } catch (InterruptedException ie) {
-            }
-        }
-
-        public void run() {
-
-            try {
-                session.getWorkspace().copy(sourcePath, destPath);
-                session.save();
-
-                randomSleep();
-            } catch (RepositoryException e) {
-                // expected
-            } finally {
-                session.logout();
-            }
-
-        }
-    }
-
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core;
+
+import org.apache.jackrabbit.test.AbstractJCRTest;
+
+import javax.jcr.NodeIterator;
+import javax.jcr.Session;
+import javax.jcr.RepositoryException;
+import java.util.Random;
+
+
+public class ConcurrentWorkspaceCopyTest extends AbstractJCRTest {
+
+    private static final int NUM_ITERATIONS = 40;
+    private static final int NUM_SESSIONS = 2;
+
+    static final String TARGET_NAME = "copy of src";
+    String sourcePath;
+    String destParentPath;
+    String destPath;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // create a parent node where allowSameNameSiblings is set to false
+        destParentPath =
+                testRootNode.addNode("destParent",
+                        "nt:folder").getPath();
+        destPath = destParentPath + "/" + TARGET_NAME;
+        // create a source node
+        sourcePath = testRootNode.addNode("src", "nt:folder").getPath();
+
+        testRootNode.getSession().save();
+    }
+
+    public void testConcurrentCopy() throws Exception {
+        for (int n = 0; n < NUM_ITERATIONS; n++) {
+            // cleanup
+            while (superuser.nodeExists(destPath)) {
+                superuser.getNode(destPath).remove();
+                superuser.save();
+            }
+
+            Thread[] threads = new Thread[NUM_SESSIONS];
+            for (int i = 0; i < threads.length; i++) {
+                // create new session
+                Session session = getHelper().getSuperuserSession();
+                String id = "session#" + i;
+                TestSession ts = new TestSession(id, session);
+                Thread t = new Thread(ts);
+                t.setName(id);
+                t.start();
+                threads[i] = t;
+            }
+            for (int i = 0; i < threads.length; i++) {
+                threads[i].join();
+            }
+
+            NodeIterator results = superuser.getNode(destParentPath).getNodes(TARGET_NAME);
+
+            assertEquals(1, results.getSize());
+        }
+    }
+
+
+    // -------------------------------------------------------< inner classes >
+    class TestSession implements Runnable {
+
+        Session session;
+        String identity;
+        Random r;
+
+        TestSession(String identity, Session s) {
+            session = s;
+            this.identity = identity;
+            r = new Random();
+        }
+
+        private void randomSleep() {
+            long l = r.nextInt(90) + 20;
+            try {
+                Thread.sleep(l);
+            } catch (InterruptedException ie) {
+            }
+        }
+
+        public void run() {
+
+            try {
+                session.getWorkspace().copy(sourcePath, destPath);
+                session.save();
+
+                randomSleep();
+            } catch (RepositoryException e) {
+                // expected
+            } finally {
+                session.logout();
+            }
+
+        }
+    }
+
 }
\ No newline at end of file
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConsistencyCheck.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConsistencyCheck.java
index ed188af..05d5ccf 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConsistencyCheck.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ConsistencyCheck.java
@@ -1,52 +1,48 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core;
-
-import org.apache.jackrabbit.core.persistence.check.ConsistencyReport;
-import org.apache.jackrabbit.test.AbstractJCRTest;
-import org.apache.jackrabbit.test.LogPrintWriter;
-
-public class ConsistencyCheck extends AbstractJCRTest {
-
-    private LogPrintWriter log = new LogPrintWriter(logger);
-
-    // Why are we running these twice?
-    public void testDo1() throws Exception {
-        runCheck();
-    }
-
-    // ...because AbstractJCRTests iterates through multiple test repositories.
-    // this way, we should check at least two of them. Yes, this is a hack.
-    public void testDo2() throws Exception {
-        runCheck();
-    }
-
-    private void runCheck() throws Exception {
-        log.print("running consistency check on repository "
-                + getHelper().getRepository());
-
-        ConsistencyReport rep = TestHelper.checkConsistency(testRootNode
-                .getSession(), false);
-        assertEquals("Found broken nodes in repository: " + rep, 0, rep
-                .getItems().size());
-
-        rep = TestHelper
-                .checkVersionStoreConsistency(testRootNode.getSession(), false);
-        assertEquals("Found broken nodes in version storage: " + rep, 0, rep
-                .getItems().size());
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core;
+
+import org.apache.jackrabbit.core.persistence.check.ConsistencyReport;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.jackrabbit.test.LogPrintWriter;
+
+public class ConsistencyCheck extends AbstractJCRTest {
+
+    private LogPrintWriter log = new LogPrintWriter(logger);
+
+    // Why are we running these twice?
+    public void testDo1() throws Exception {
+        runCheck();
+    }
+
+    // ...because AbstractJCRTests iterates through multiple test repositories.
+    // this way, we should check at least two of them. Yes, this is a hack.
+    public void testDo2() throws Exception {
+        runCheck();
+    }
+
+    private void runCheck() throws Exception {
+        log.print("running consistency check on repository "
+                + getHelper().getRepository());
+
+        ConsistencyReport rep = TestHelper.checkConsistency(testRootNode.getSession(), false, null);
+        assertEquals("Found broken nodes in repository: " + rep, 0, rep.getItems().size());
+
+        rep = TestHelper.checkVersionStoreConsistency(testRootNode.getSession(), false, null);
+        assertEquals("Found broken nodes in version storage: " + rep, 0, rep.getItems().size());
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/MoveRemoveTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/MoveRemoveTest.java
new file mode 100644
index 0000000..e62dac6
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/MoveRemoveTest.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core;
+
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.core.persistence.check.ConsistencyReport;
+import org.apache.jackrabbit.core.state.StaleItemStateException;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.jackrabbit.test.NotExecutableException;
+
+public class MoveRemoveTest extends AbstractJCRTest {
+
+    private String folder1Path;
+    private String folder2Path;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        Node folder1 = testRootNode.addNode("folder1");
+        folder1Path = folder1.getPath();
+        Node folder2 = testRootNode.addNode("folder2");
+        folder2Path = folder2.getPath();
+        folder1.addNode("node");
+        testRootNode.getSession().save();
+    }
+
+    public void testMoveRemove() throws RepositoryException, NotExecutableException {
+        Session session1 = getHelper().getSuperuserSession();
+        Session session2 = getHelper().getSuperuserSession();
+        session1.move(folder1Path + "/node", folder2Path + "/node");
+        session2.getNode(folder1Path + "/node").remove();
+        session1.save();
+        try {
+            session2.save();
+        } catch (InvalidItemStateException e) {
+            if (e.getCause() == null || e.getCause().getClass() != StaleItemStateException.class) {
+                throw e;
+            }
+        }
+        ConsistencyReport consistencyReport = TestHelper.checkConsistency(testRootNode.getSession(), false, null);
+        //for (ReportItem item : consistencyReport.getItems()) {
+        //    System.out.println(item.getMessage());
+        //}
+        assertTrue(consistencyReport.getItems().size() == 0);
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/MoveTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/MoveTest.java
index 6f73be8..6d39a27 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/MoveTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/MoveTest.java
@@ -103,4 +103,52 @@ public class MoveTest extends AbstractJCRTest {
         assertEquals(testRootNode.getPath() + "/" + nodeName2, now);
     }
 
+    /**
+     * Test reordering same-name-siblings using move
+     */
+    public void testReorderSameNameSiblingsUsingMove() throws RepositoryException {
+        Session session = testRootNode.getSession();
+        for (NodeIterator it = testRootNode.getNodes(); it.hasNext();) {
+            it.nextNode().remove();
+            session.save();
+        }
+        Node node1 = testRootNode.addNode(nodeName1);
+        Node node2 = testRootNode.addNode(nodeName1);
+        String path = node1.getPath();
+
+        // re-order the nodes using move
+        session.move(path, path);
+
+        assertEquals(path + "[2]", node1.getPath());
+        assertEquals(path, node2.getPath());
+    }
+
+    /**
+     * Verify paths of same name siblings are correct after a (reordering) move
+     * Issue JCR-1880
+     */
+    public void testGetPathDoesNotInfluencePathsAfterMove() throws RepositoryException {
+        doTestMoveWithGetPath(false);
+        doTestMoveWithGetPath(true);
+    }
+
+    private void doTestMoveWithGetPath(boolean index) throws RepositoryException {
+        Session session = testRootNode.getSession();
+        for (NodeIterator it = testRootNode.getNodes(); it.hasNext();) {
+            it.nextNode().remove();
+            session.save();
+        }
+        String testPath = testRootNode.getPath();
+        Node a = testRootNode.addNode("a");
+        Node b = a.addNode("b");
+        session.save();
+        session.move(testPath + "/a/b", testPath + "/a");
+        if (index) {
+            b.getPath();
+        }
+        session.move(testPath + "/a", testPath + "/a");
+        assertEquals(testPath + "/a[2]", a.getPath());
+        assertEquals(testPath + "/a", b.getPath());
+    }
+
 }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/NodeImplTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/NodeImplTest.java
index 7ac593f..1310bcc 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/NodeImplTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/NodeImplTest.java
@@ -397,4 +397,16 @@ public class NodeImplTest extends AbstractJCRTest {
         root.remove();
         session.save();
     }
+
+    public void testBracketsInNodeName() throws Exception {
+        final Node root = testRootNode.addNode("testBracketsInNodeName");
+
+        final String[] childNames = { "{A}", "B}", "{C", "(D)", "E)", "(F", };
+
+        for (String name : childNames) {
+            root.addNode(name);
+            root.getSession().save();
+            assertTrue("Expecting child " + name + " to have been created", root.hasNode(name));
+        }
+    }
 }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/OverlappingNodeAddTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/OverlappingNodeAddTest.java
index f618407..d5e71dc 100755
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/OverlappingNodeAddTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/OverlappingNodeAddTest.java
@@ -1,132 +1,132 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core;
-
-import javax.jcr.InvalidItemStateException;
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.Node;
-import javax.jcr.Session;
-
-import org.apache.jackrabbit.test.AbstractJCRTest;
-
-public class OverlappingNodeAddTest extends AbstractJCRTest {
-
-    private Node testfolder;
-    private Session s1;
-    private Session s2;
-
-    public void setUp() throws Exception {
-        super.setUp();
-        testfolder = testRootNode.addNode("container", "nt:folder");
-        s1 = testfolder.getSession();
-        s2 = getHelper().getReadWriteSession();
-        s1.save();
-    }
-
-    /**
-     * Performs add operations on a single node (no SNS) through 2 sessions
-     */
-    public void testWith2Folders() throws Exception {
-
-        boolean bWasSaved = false;
-
-        String testpath = testfolder.getPath();
-
-        Node f1 = s1.getNode(testpath).addNode("folder", "nt:folder");
-        Node c1 = f1.addNode("a", "nt:file");
-        Node r1 = c1.addNode("jcr:content", "nt:resource");
-        r1.setProperty("jcr:data", "foo");
-
-        Node f2 = s2.getNode(testpath).addNode("folder", "nt:folder");
-        Node c2 = f2.addNode("b", "nt:file");
-        Node r2 = c2.addNode("jcr:content", "nt:resource");
-        r2.setProperty("jcr:data", "bar");
-
-        s1.save();
-
-        String s1FolderId = f1.getIdentifier();
-
-        try {
-            s2.save();
-            bWasSaved = true;
-        } catch (InvalidItemStateException ex) {
-            // expected
-
-            // retry; adding refresh doesn't change anything here
-            try {
-                s2.save();
-
-                bWasSaved = true;
-
-            } catch (InvalidItemStateException ex2) {
-                // we would be cool with this
-            }
-        }
-
-        // we don't have changes in s1, so the keepChanges flag should be
-        // irrelevant
-        s1.refresh(false);
-
-        // be nice and get a new Node instance
-        Node newf1 = s1.getNode(testpath + "/folder");
-
-        // if bWasSaved it should now be visible to Session 1
-        assertEquals("'b' was saved, so session 1 should see it", bWasSaved,
-                newf1.hasNode("b"));
-
-        // 'a' was saved by Session 1 earlier on
-        if (!newf1.hasNode("a")) {
-            String message = "child node 'a' not present";
-
-            if (bWasSaved && !s1FolderId.equals(newf1.getIdentifier())) {
-                message += ", and also the folder's identifier changed from "
-                        + s1FolderId + " to " + newf1.getIdentifier();
-            }
-
-            Node oldf1 = null;
-
-            try {
-                oldf1 = s1.getNodeByIdentifier(s1FolderId);
-            } catch (ItemNotFoundException ex) {
-                message += "; node with id "
-                        + s1FolderId
-                        + " can't be retrieved using getNodeByIdentifier either";
-            }
-
-            if (oldf1 != null) {
-                try {
-                    oldf1.getPath();
-                } catch (Exception ex) {
-                    message += "; node with id "
-                            + s1FolderId
-                            + " can be retrieved using getNodeByIdentifier, but getPath() fails with: "
-                            + ex.getMessage();
-                }
-            }
-
-            fail(message);
-        }
-    }
-
-    protected void tearDown() throws Exception {
-        if (s2 != null) {
-            s2.logout();
-            s2 = null;
-        }
-        super.tearDown();
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core;
+
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.test.AbstractJCRTest;
+
+public class OverlappingNodeAddTest extends AbstractJCRTest {
+
+    private Node testfolder;
+    private Session s1;
+    private Session s2;
+
+    public void setUp() throws Exception {
+        super.setUp();
+        testfolder = testRootNode.addNode("container", "nt:folder");
+        s1 = testfolder.getSession();
+        s2 = getHelper().getReadWriteSession();
+        s1.save();
+    }
+
+    /**
+     * Performs add operations on a single node (no SNS) through 2 sessions
+     */
+    public void testWith2Folders() throws Exception {
+
+        boolean bWasSaved = false;
+
+        String testpath = testfolder.getPath();
+
+        Node f1 = s1.getNode(testpath).addNode("folder", "nt:folder");
+        Node c1 = f1.addNode("a", "nt:file");
+        Node r1 = c1.addNode("jcr:content", "nt:resource");
+        r1.setProperty("jcr:data", "foo");
+
+        Node f2 = s2.getNode(testpath).addNode("folder", "nt:folder");
+        Node c2 = f2.addNode("b", "nt:file");
+        Node r2 = c2.addNode("jcr:content", "nt:resource");
+        r2.setProperty("jcr:data", "bar");
+
+        s1.save();
+
+        String s1FolderId = f1.getIdentifier();
+
+        try {
+            s2.save();
+            bWasSaved = true;
+        } catch (InvalidItemStateException ex) {
+            // expected
+
+            // retry; adding refresh doesn't change anything here
+            try {
+                s2.save();
+
+                bWasSaved = true;
+
+            } catch (InvalidItemStateException ex2) {
+                // we would be cool with this
+            }
+        }
+
+        // we don't have changes in s1, so the keepChanges flag should be
+        // irrelevant
+        s1.refresh(false);
+
+        // be nice and get a new Node instance
+        Node newf1 = s1.getNode(testpath + "/folder");
+
+        // if bWasSaved it should now be visible to Session 1
+        assertEquals("'b' was saved, so session 1 should see it", bWasSaved,
+                newf1.hasNode("b"));
+
+        // 'a' was saved by Session 1 earlier on
+        if (!newf1.hasNode("a")) {
+            String message = "child node 'a' not present";
+
+            if (bWasSaved && !s1FolderId.equals(newf1.getIdentifier())) {
+                message += ", and also the folder's identifier changed from "
+                        + s1FolderId + " to " + newf1.getIdentifier();
+            }
+
+            Node oldf1 = null;
+
+            try {
+                oldf1 = s1.getNodeByIdentifier(s1FolderId);
+            } catch (ItemNotFoundException ex) {
+                message += "; node with id "
+                        + s1FolderId
+                        + " can't be retrieved using getNodeByIdentifier either";
+            }
+
+            if (oldf1 != null) {
+                try {
+                    oldf1.getPath();
+                } catch (Exception ex) {
+                    message += "; node with id "
+                            + s1FolderId
+                            + " can be retrieved using getNodeByIdentifier, but getPath() fails with: "
+                            + ex.getMessage();
+                }
+            }
+
+            fail(message);
+        }
+    }
+
+    protected void tearDown() throws Exception {
+        if (s2 != null) {
+            s2.logout();
+            s2 = null;
+        }
+        super.tearDown();
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ReadVersionsWhileModified.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ReadVersionsWhileModified.java
index 68a9b31..a092d46 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ReadVersionsWhileModified.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ReadVersionsWhileModified.java
@@ -25,7 +25,7 @@ import javax.jcr.version.Version;
 /**
  * <code>ReadVersionsWhileModified</code> tests if version history can be read
  * consistently while it is modified by another thread.
- * <p/>
+ * <p>
  * This is a test case for: <a href="http://issues.apache.org/jira/browse/JCR-18">
  * JCR-18</a>
  */
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/RemoveAddNodeWithUUIDTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/RemoveAddNodeWithUUIDTest.java
new file mode 100644
index 0000000..95e142a
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/RemoveAddNodeWithUUIDTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core;
+
+import java.io.File;
+
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.Node;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.test.AbstractJCRTest;
+
+/**
+ * <code>RemoveAddNodeWithUUIDTest</code> check if no 'overwriting cached entry'
+ * warnings are written to the log when a node is re-created with the same UUID.
+ * See: JCR-3419
+ */
+public class RemoveAddNodeWithUUIDTest extends AbstractJCRTest {
+
+    public void testRemoveAdd() throws Exception {
+        Tail tail = Tail.start(new File("target", "jcr.log"), "overwriting cached entry");
+        try {
+            Node test = testRootNode.addNode("test");
+            test.setProperty("prop", 1);
+            test.addMixin(mixReferenceable);
+            superuser.save();
+            String testId = test.getIdentifier();
+
+            Session s = getHelper().getSuperuserSession();
+            try {
+                Node testOther = s.getNode(test.getPath());
+
+                test.remove();
+                test = ((NodeImpl) testRootNode).addNodeWithUuid("test", testId);
+                test.setProperty("prop", 2);
+                superuser.save();
+
+                // now test node instance is not accessible anymore for s
+                try {
+                    testOther.getProperty("prop");
+                    fail("test node instance must not be accessibly anymore");
+                } catch (InvalidItemStateException e) {
+                    // expected
+                }
+                // getting it again must succeed and return updated property value
+                testOther = s.getNode(test.getPath());
+                assertEquals("property outdated", 2, testOther.getProperty("prop").getLong());
+
+                assertFalse("detected 'overwriting cached entry' messages in log", tail.getLines().iterator().hasNext());
+            } finally {
+                s.logout();
+            }
+        } finally {
+            tail.close();
+        }
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ReplacePropertyWhileOthersReadTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ReplacePropertyWhileOthersReadTest.java
new file mode 100644
index 0000000..4741294
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/ReplacePropertyWhileOthersReadTest.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>ReplacePropertyWhileOthersReadTest</code>...
+ */
+public class ReplacePropertyWhileOthersReadTest extends AbstractJCRTest {
+
+    private static final Logger log = LoggerFactory.getLogger(ReplacePropertyWhileOthersReadTest.class);
+
+    private final List<Value> values = new ArrayList<Value>();
+
+    private Node test;
+
+    private final Random rand = new Random();
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        test = testRootNode.addNode("test");
+        test.addMixin(mixReferenceable);
+        superuser.save();
+        values.add(vf.createValue("value"));
+        values.add(vf.createValue(new BigDecimal(1234)));
+        values.add(vf.createValue(Calendar.getInstance()));
+        values.add(vf.createValue(1.234));
+        values.add(vf.createValue(true));
+        values.add(vf.createValue(test));
+        values.add(vf.createValue(vf.createBinary(
+                new ByteArrayInputStream(new byte[0]))));
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        test = null;
+        values.clear();
+        super.tearDown();
+    }
+
+    public void testAddRemove() throws Exception {
+        final Property prop = test.setProperty("prop", getRandomValue());
+        superuser.save();
+
+        Thread reader = new Thread(new Runnable() {
+
+            String path = prop.getPath();
+
+            public void run() {
+                // run for three seconds
+                long stop = System.currentTimeMillis()
+                        + TimeUnit.SECONDS.toMillis(3);
+                while (System.currentTimeMillis() < stop) {
+                    try {
+                        Session s = getHelper().getSuperuserSession();
+                        try {
+                            s.getProperty(path);
+                        } finally {
+                            s.logout();
+                        }
+                    } catch (RepositoryException e) {
+                        log.warn("", e);
+                    }
+                }
+            }
+        });
+        Tail tail = Tail.start(new File("target", "jcr.log"),
+                "overwriting cached entry");
+        try {
+            reader.start();
+            while (reader.isAlive()) {
+                test.getProperty("prop").remove();
+                int type;
+                boolean isMultivalued;
+                if (rand.nextBoolean()) {
+                    Value v = getRandomValue();
+                    isMultivalued = false;
+                    type = v.getType();
+                    test.setProperty("prop", v);
+                } else {
+                    Value[] v = getRandomMultiValue();
+                    type = v[0].getType();
+                    isMultivalued = true;
+                    test.setProperty("prop", v);
+                }
+                superuser.save();
+                assertEquals(isMultivalued, test.getProperty("prop").isMultiple());
+                assertEquals(type, test.getProperty("prop").getType());
+            }
+            assertFalse("detected 'overwriting cached entry' messages in log",
+                    tail.getLines().iterator().hasNext());
+        } finally {
+            tail.close();
+        }
+    }
+
+    private Value getRandomValue() {
+        return values.get(rand.nextInt(values.size()));
+    }
+
+    private Value[] getRandomMultiValue() {
+        return new Value[]{getRandomValue()};
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/Tail.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/Tail.java
new file mode 100644
index 0000000..2af715d
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/Tail.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core;
+
+import java.io.BufferedReader;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Iterator;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.commons.iterator.FilterIterator;
+import org.apache.jackrabbit.commons.predicate.Predicate;
+
+/**
+ * <code>Tail</code> is a test utility class to tail and grep a text file.
+ */
+public class Tail implements Closeable {
+
+    private final String grep;
+
+    private final BufferedReader reader;
+
+    private Tail(File file, String grep) throws IOException {
+        this.grep = grep;
+        this.reader = new BufferedReader(new InputStreamReader(
+                new FileInputStream(file)));
+        while (reader.skip(Integer.MAX_VALUE) > 0) {
+            // skip more, until end of file
+        }
+    }
+
+    /**
+     * Create a tail on the given <code>file</code> with an optional string to
+     * match lines.
+     *
+     * @param file the file to tail.
+     * @param grep the string to match or <code>null</code> if all lines should
+     *             be returned.
+     * @return a tail on the file.
+     * @throws IOException if the files does not exist or some other I/O error
+     *                     occurs.
+     */
+    public static Tail start(File file, String grep) throws IOException {
+        return new Tail(file, grep);
+    }
+
+    /**
+     * Returns the lines that were written to the file since
+     * <code>Tail.start()</code> or the last call to <code>getLines()</code>.
+     *
+     * @return the matching lines.
+     * @throws IOException if an error occurs while reading from the file.
+     */
+    public Iterable<String> getLines() throws IOException {
+        return new Iterable<String>() {
+            public Iterator<String> iterator() {
+                Iterator<String> it = IOUtils.lineIterator(reader);
+                if (grep == null || grep.length() == 0) {
+                    return it;
+                } else {
+                    // filter
+                    return new FilterIterator<String>(it, new Predicate() {
+                        public boolean evaluate(Object o) {
+                            return o.toString().contains(grep);
+                        }
+                    });
+                }
+            }
+        };
+    }
+
+    /**
+     * Releases the underlying stream from the file.
+     *
+     * @throws IOException If an I/O error occurs.
+     */
+    public void close() throws IOException {
+        reader.close();
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/TestAll.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/TestAll.java
index ba4e051..d4c2129 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/TestAll.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/TestAll.java
@@ -34,6 +34,7 @@ public class TestAll extends TestCase {
     public static Test suite() {
         TestSuite suite = new ConcurrentTestSuite("Core tests");
 
+        suite.addTestSuite(ReplacePropertyWhileOthersReadTest.class);
         suite.addTestSuite(CachingHierarchyManagerTest.class);
         suite.addTestSuite(ShareableNodeTest.class);
         suite.addTestSuite(MultiWorkspaceShareableNodeTest.class);
@@ -74,12 +75,11 @@ public class TestAll extends TestCase {
         }
 
         suite.addTestSuite(UserPerWorkspaceSecurityManagerTest.class);
-
         suite.addTestSuite(OverlappingNodeAddTest.class);
-
         suite.addTestSuite(NPEandCMETest.class);
-
         suite.addTestSuite(ConsistencyCheck.class);
+        suite.addTestSuite(RemoveAddNodeWithUUIDTest.class);
+        suite.addTestSuite(MoveAtRootTest.class);
 
         return suite;
     }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/TestHelper.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/TestHelper.java
index 34a4248..84aa177 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/TestHelper.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/TestHelper.java
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.core;
 
+import java.io.IOException;
 import java.util.concurrent.TimeUnit;
 
 import javax.jcr.Repository;
@@ -25,6 +26,9 @@ import javax.jcr.Session;
 import org.apache.jackrabbit.core.persistence.PersistenceManager;
 import org.apache.jackrabbit.core.persistence.check.ConsistencyChecker;
 import org.apache.jackrabbit.core.persistence.check.ConsistencyReport;
+import org.apache.jackrabbit.core.query.QueryHandler;
+import org.apache.jackrabbit.core.query.lucene.ConsistencyCheck;
+import org.apache.jackrabbit.core.query.lucene.SearchIndex;
 import org.apache.jackrabbit.test.NotExecutableException;
 
 /**
@@ -50,13 +54,14 @@ public class TestHelper {
      *
      * @param session the Session accessing the workspace to be checked
      * @param runFix whether to attempt fixup
+     * @param lostNFoundId node to which to attach orphaned nodes (or <code>null</code>)
      * @throws RepositoryException if an error occurs while getting the
      * workspace with the given name.
      * @throws NotExecutableException if the {@link PersistenceManager} does
      * not implement {@link ConsistencyChecker}, or if the associated
      * {@link Repository} is not a {@link RepositoryImpl}.
      */
-    public static ConsistencyReport checkConsistency(Session session, boolean runFix)
+    public static ConsistencyReport checkConsistency(Session session, boolean runFix, String lostNFoundId)
             throws NotExecutableException, RepositoryException {
         Repository r = session.getRepository();
         if (!(r instanceof RepositoryImpl)) {
@@ -68,22 +73,38 @@ public class TestHelper {
             if (!(pm instanceof ConsistencyChecker)) {
                 throw new NotExecutableException();
             } else {
-                return ((ConsistencyChecker) pm).check(null, true, runFix);
+                return ((ConsistencyChecker) pm).check(null, true, runFix, lostNFoundId, null);
             }
         }
     }
 
+    public static ConsistencyCheck checkIndexConsistency(Session session) throws RepositoryException, NotExecutableException, IOException {
+        Repository r = session.getRepository();
+        if (!(r instanceof RepositoryImpl)) {
+            throw new NotExecutableException();
+        }
+        RepositoryImpl ri = (RepositoryImpl) r;
+        final String workspaceName = session.getWorkspace().getName();
+        QueryHandler qh = ri.getSearchManager(workspaceName).getQueryHandler();
+        if (!(qh instanceof SearchIndex)) {
+            throw new NotExecutableException("No search index");
+        }
+        SearchIndex si = (SearchIndex) qh;
+        return si.runConsistencyCheck();
+    }
+
     /**
      * Runs a consistency check on the versioning store used by the specified session.
      *
      * @param session the Session accessing the workspace to be checked
      * @param runFix whether to attempt fixup
+     * @param lostNFoundId node to which to attach orphaned nodes (or <code>null</code>)
      * @throws RepositoryException
      * @throws NotExecutableException if the {@link PersistenceManager} does
      * not implement {@link ConsistencyChecker}, or if the associated
      * {@link Repository} is not a {@link RepositoryImpl}.
      */
-    public static ConsistencyReport checkVersionStoreConsistency(Session session, boolean runFix)
+    public static ConsistencyReport checkVersionStoreConsistency(Session session, boolean runFix, String lostNFoundId)
             throws NotExecutableException, RepositoryException {
         Repository r = session.getRepository();
         if (!(r instanceof RepositoryImpl)) {
@@ -95,7 +116,7 @@ public class TestHelper {
             if (!(pm instanceof ConsistencyChecker)) {
                 throw new NotExecutableException();
             } else {
-                return ((ConsistencyChecker) pm).check(null, true, runFix);
+                return ((ConsistencyChecker) pm).check(null, true, runFix, lostNFoundId, null);
             }
         }
     }
@@ -112,4 +133,13 @@ public class TestHelper {
             TimeUnit.MILLISECONDS.sleep(100);
         }
     }
+
+    public static SearchManager getSearchManager(Session session) throws NotExecutableException, RepositoryException {
+        Repository r = session.getRepository();
+        if (!(r instanceof RepositoryImpl)) {
+            throw new NotExecutableException();
+        }
+        RepositoryImpl ri = (RepositoryImpl) r;
+        return ri.getSearchManager(session.getWorkspace().getName());
+    }
 }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/XATest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/XATest.java
index 118da84..996c91c 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/XATest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/XATest.java
@@ -668,16 +668,16 @@ public class XATest extends AbstractJCRTest {
     /**
      * Checks if getReferences() reflects an added reference property that has
      * been saved but not yet committed.
-     * <p/>
+     * <p>
      * Spec say:
-     * <p/>
+     * <p>
      * <i>Some level 2 implementations may only return properties that have been
      * saved (in a transactional setting this includes both those properties
      * that have been saved but not yet committed, as well as properties that
      * have been committed). Other level 2 implementations may additionally
      * return properties that have been added within the current Session but are
      * not yet saved.</i>
-     * <p/>
+     * <p>
      * Jackrabbit does not support the latter, but at least has to support the
      * first.
      */
@@ -941,14 +941,21 @@ public class XATest extends AbstractJCRTest {
         n.save();
 
         superuser.removeLockToken(lockToken);
-        assertNull("session must get a null lock token", lock.getLockToken());
+
+        String nlt = lock.getLockToken();
+        assertTrue("freshly obtained lock token must either be null or the same as the one returned earlier",
+                nlt == null || nlt.equals(lockToken));
+
         assertFalse("session must not hold lock token", containsLockToken(superuser, lockToken));
         
         // commit
         utx.commit();
 
+        nlt = lock.getLockToken();
+        assertTrue("freshly obtained lock token must either be null or the same as the one returned earlier",
+                nlt == null || nlt.equals(lockToken));
+
         assertFalse("session must not hold lock token", containsLockToken(superuser, lockToken));
-        assertNull("session must get a null lock token", lock.getLockToken());
 
         // start new Transaction and try to unlock
         utx = new UserTransactionImpl(superuser);
@@ -1141,7 +1148,10 @@ public class XATest extends AbstractJCRTest {
         assertTrue("session must hold lock token", containsLockToken(superuser, lockToken));
 
         superuser.removeLockToken(lockToken);
-        assertNull("session must get a null lock token", lock.getLockToken());
+
+        String nlt = lock.getLockToken();
+        assertTrue("freshly obtained lock token must either be null or the same as the one returned earlier",
+                nlt == null || nlt.equals(lockToken));
 
         // commit
         utx.commit();
@@ -1149,7 +1159,9 @@ public class XATest extends AbstractJCRTest {
         // refresh Lock Info
         lock = n.getLock();
 
-        assertNull("session must get a null lock token", lock.getLockToken());
+        nlt = lock.getLockToken();
+        assertTrue("freshly obtained lock token must either be null or the same as the one returned earlier",
+                nlt == null || nlt.equals(lockToken));
 
         Session other = getHelper().getSuperuserSession();
         try {
@@ -1910,15 +1922,20 @@ public class XATest extends AbstractJCRTest {
         assertTrue("session must hold lock token", containsLockToken(superuser, lockToken));
 
         superuser.removeLockToken(lockToken);
-        assertNull("session must get a null lock token", lock.getLockToken());
-        
+
+        String nlt = lock.getLockToken();
+        assertTrue("freshly obtained lock token must either be null or the same as the one returned earlier",
+                nlt == null || nlt.equals(lockToken));
+
         // commit
         utx.commit();
         
         // refresh Lock Info
         lock = n.getLock();
 
-        assertNull("session must get a null lock token", lock.getLockToken());
+        nlt = lock.getLockToken();
+        assertTrue("freshly obtained lock token must either be null or the same as the one returned earlier",
+                nlt == null || nlt.equals(lockToken));
 
         Session other = getHelper().getSuperuserSession();
         // start new Transaction and try to add lock token unlock the node and then remove it
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/ClusterDescriptorTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/ClusterDescriptorTest.java
new file mode 100644
index 0000000..2727bf5
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/ClusterDescriptorTest.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.cluster;
+
+import java.io.File;
+import java.io.IOException;
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.apache.jackrabbit.test.JUnitTest;
+
+/**
+ * Tests clustering with a database.
+ */
+public class ClusterDescriptorTest extends JUnitTest {
+
+    private RepositoryImpl rep1, rep2;
+
+    public void setUp() throws Exception {
+        deleteAll();
+        FileUtils.copyFile(
+                new File("./src/test/resources/org/apache/jackrabbit/core/cluster/repository-h2.xml"),
+                new File("./target/descriptorClusterTest/node1/repository.xml"));
+        FileUtils.copyFile(
+                new File("./src/test/resources/org/apache/jackrabbit/core/cluster/repository-h2.xml"),
+                new File("./target/descriptorClusterTest/node2/repository.xml"));
+
+        rep1 = RepositoryImpl.create(RepositoryConfig.create(
+                new File("./target/descriptorClusterTest/node1")));
+        rep2 = RepositoryImpl.create(RepositoryConfig.create(
+                new File("./target/descriptorClusterTest/node2")));
+    }
+
+    public void tearDown() throws Exception {
+    		rep1.shutdown();
+	    rep2.shutdown();
+        deleteAll();
+    }
+
+    private static void deleteAll() throws IOException {
+        FileUtils.deleteDirectory(new File("./target/descriptorClusterTest"));
+    }
+
+    public void testRepositoryDescriptor() {
+        String clusterId1 =  rep1.getDescriptor(RepositoryImpl.JACKRABBIT_CLUSTER_ID);
+        String clusterId2 =  rep2.getDescriptor(RepositoryImpl.JACKRABBIT_CLUSTER_ID);
+
+        assertNotNull("Cluster descriptor not set for cluster node 1", clusterId1);
+        assertNotNull("Cluster descriptor not set for cluster node 2", clusterId2);
+
+        assertFalse("Cluster ids should be unique", clusterId1.equals(clusterId2));
+    }
+
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/DbClusterTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/DbClusterTest.java
index c663beb..5c12cc1 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/DbClusterTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/DbClusterTest.java
@@ -25,21 +25,15 @@ import org.apache.commons.io.FileUtils;
 import org.apache.jackrabbit.core.RepositoryImpl;
 import org.apache.jackrabbit.core.config.RepositoryConfig;
 import org.apache.jackrabbit.test.JUnitTest;
-import org.h2.tools.Server;
 
 /**
  * Tests clustering with a database.
  */
 public class DbClusterTest extends JUnitTest {
 
-    Server server1, server2;
-
     public void setUp() throws Exception {
         deleteAll();
-        server1 = Server.createTcpServer("-tcpPort", "9001", "-baseDir",
-                "./target/dbClusterTest/db1", "-tcpAllowOthers").start();
-        server2 = Server.createTcpServer("-tcpPort", "9002", "-baseDir",
-                "./target/dbClusterTest/db2", "-tcpAllowOthers").start();
+
         FileUtils.copyFile(
                 new File("./src/test/resources/org/apache/jackrabbit/core/cluster/repository-h2.xml"),
                 new File("./target/dbClusterTest/node1/repository.xml"));
@@ -49,12 +43,10 @@ public class DbClusterTest extends JUnitTest {
     }
 
     public void tearDown() throws Exception {
-        server1.stop();
-        server2.stop();
         deleteAll();
     }
 
-    private void deleteAll() throws IOException {
+    private static void deleteAll() throws IOException {
         FileUtils.deleteDirectory(new File("./target/dbClusterTest"));
     }
 
@@ -65,12 +57,14 @@ public class DbClusterTest extends JUnitTest {
                 new File("./target/dbClusterTest/node2")));
         Session s1 = rep1.login(new SimpleCredentials("admin", "admin".toCharArray()));
         Session s2 = rep2.login(new SimpleCredentials("admin", "admin".toCharArray()));
+
         s1.getRootNode().addNode("test1");
         s2.getRootNode().addNode("test2");
         s1.save();
         s2.save();
         s1.refresh(true);
         s2.refresh(true);
+
         s1.getRootNode().getNode("test2");
         s2.getRootNode().getNode("test1");
         rep1.shutdown();
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/DbClusterTestJCR3162.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/DbClusterTestJCR3162.java
index 6f7067d..d3f785a 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/DbClusterTestJCR3162.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/DbClusterTestJCR3162.java
@@ -38,7 +38,6 @@ import org.apache.jackrabbit.commons.JcrUtils;
 import org.apache.jackrabbit.core.RepositoryImpl;
 import org.apache.jackrabbit.core.config.RepositoryConfig;
 import org.apache.jackrabbit.test.JUnitTest;
-import org.h2.tools.Server;
 
 /**
  * Test for JCR3162
@@ -48,9 +47,6 @@ public class DbClusterTestJCR3162 extends JUnitTest {
     private static final SimpleCredentials ADMIN = new SimpleCredentials(
             "admin", "admin".toCharArray());
 
-    private Server server1;
-    private Server server2;
-
     private RepositoryImpl rep1;
     private RepositoryImpl rep2;
 
@@ -59,10 +55,6 @@ public class DbClusterTestJCR3162 extends JUnitTest {
 
     public void setUp() throws Exception {
         deleteAll();
-        server1 = Server.createTcpServer("-tcpPort", "9001", "-baseDir",
-                "./target/dbClusterTest/db1", "-tcpAllowOthers").start();
-        server2 = Server.createTcpServer("-tcpPort", "9002", "-baseDir",
-                "./target/dbClusterTest/db2", "-tcpAllowOthers").start();
         FileUtils
                 .copyFile(
                         new File(
@@ -89,13 +81,11 @@ public class DbClusterTestJCR3162 extends JUnitTest {
                 rep2.shutdown();
             }
         } finally {
-            server1.stop();
-            server2.stop();
             deleteAll();
         }
     }
 
-    private void deleteAll() throws IOException {
+    private static void deleteAll() throws IOException {
         FileUtils.deleteDirectory(new File("./target/dbClusterTest"));
     }
 
@@ -133,8 +123,7 @@ public class DbClusterTestJCR3162 extends JUnitTest {
         Connection con = null;
         try {
             con = DriverManager.getConnection(
-                    "jdbc:h2:tcp://localhost:9001,localhost:9002/db", "sa",
-                    "sa");
+                    "jdbc:h2:./target/dbClusterTest/db", "sa", "sa");
             PreparedStatement prep = con
                     .prepareStatement("update JOURNAL_LOCAL_REVISIONS set REVISION_ID=0 where JOURNAL_ID=?");
             prep.setString(1, clusterId2);
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/FailUpdateOnJournalExceptionTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/FailUpdateOnJournalExceptionTest.java
new file mode 100644
index 0000000..9e704e0
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/FailUpdateOnJournalExceptionTest.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.cluster;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.apache.jackrabbit.test.JUnitTest;
+
+/**
+ * <code>FailUpdateOnJournalExceptionTest</code> checks if
+ * UpdateEventChannel.updateCreated(Update) throws a ClusterException
+ * when locking the Journal fails. See JCR-3417
+ */
+public class FailUpdateOnJournalExceptionTest extends JUnitTest {
+
+    private RepositoryImpl repo;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        deleteAll();
+        FileUtils.copyInputStreamToFile(
+                getClass().getResourceAsStream("repository-with-test-journal.xml"),
+                new File(getTestDir(), "repository.xml"));
+        repo = RepositoryImpl.create(RepositoryConfig.create(getTestDir()));
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (repo != null) {
+            repo.shutdown();
+        }
+        deleteAll();
+        super.tearDown();
+    }
+
+    public void testUpdate() throws Exception {
+        Session s = repo.login(new SimpleCredentials("admin", "admin".toCharArray()));
+        Node root = s.getRootNode();
+        root.addNode("foo");
+        s.save();
+        root.addNode("bar");
+        TestJournal.refuseLock = true;
+        try {
+            s.save();
+            fail("Session.save() must fail with RepositoryException when Journal cannot be locked.");
+        } catch (RepositoryException e) {
+            // expected
+        } finally {
+            TestJournal.refuseLock = false;
+        }
+    }
+
+    // JCR-3783
+    public void testFailedWrite() throws Exception {
+        Session s = repo.login(new SimpleCredentials("admin", "admin".toCharArray()));
+        Node root = s.getRootNode();
+        root.addNode("foo");
+        s.save();
+        root.addNode("bar");
+        TestJournal.failRecordWrite = true;
+        try {
+            s.save();
+            fail("Session.save() must fail with RepositoryException when Journal write fails.");
+        } catch (RepositoryException e) {
+            // expected
+        } finally {
+            TestJournal.failRecordWrite = false;
+        }
+        // must succeed after refresh
+        s.refresh(false);
+        root.addNode("bar");
+        s.save();
+    }
+
+    private static void deleteAll() throws IOException {
+        FileUtils.deleteDirectory(getTestDir());
+    }
+
+    private static File getTestDir() throws IOException {
+        return new File("target",
+                FailUpdateOnJournalExceptionTest.class.getSimpleName());
+    }
+
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/TestAll.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/TestAll.java
index 5fda729..70538ff 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/TestAll.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/TestAll.java
@@ -41,6 +41,7 @@ public class TestAll extends TestCase {
         suite.addTestSuite(ClusterSyncTest.class);
         suite.addTestSuite(DbClusterTest.class);
         suite.addTestSuite(DbClusterTestJCR3162.class);
+        suite.addTestSuite(FailUpdateOnJournalExceptionTest.class);
 
         return suite;
     }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/TestJournal.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/TestJournal.java
new file mode 100644
index 0000000..edee7f9
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/cluster/TestJournal.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.cluster;
+
+import org.apache.jackrabbit.core.journal.AppendRecord;
+import org.apache.jackrabbit.core.journal.DefaultRecordProducer;
+import org.apache.jackrabbit.core.journal.JournalException;
+import org.apache.jackrabbit.core.journal.MemoryJournal;
+import org.apache.jackrabbit.core.journal.RecordProducer;
+
+/**
+* <code>TestJournal</code> extends the MemoryJournal with a static hook to
+* refuse lock acquisition.
+*/
+public final class TestJournal extends MemoryJournal {
+
+    static boolean refuseLock = false;
+
+    static boolean failRecordWrite = false;
+
+    @Override
+    protected void doLock() throws JournalException {
+        if (refuseLock) {
+            throw new JournalException("lock refused");
+        } else {
+            super.doLock();
+        }
+    }
+
+    @Override
+    protected RecordProducer createProducer(final String identifier) {
+        return new DefaultRecordProducer(this, identifier) {
+            @Override
+            protected AppendRecord createRecord() throws JournalException {
+                return new AppendRecord(TestJournal.this, identifier) {
+                    @Override
+                    public void writeString(String s) throws JournalException {
+                        if (failRecordWrite) {
+                            throw new JournalException("write failed");
+                        } else {
+                            super.writeString(s);
+                        }
+                    }
+                };
+            }
+        };
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/ConsistencyCheckerImplTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/ConsistencyCheckerImplTest.java
new file mode 100644
index 0000000..8d125cf
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/ConsistencyCheckerImplTest.java
@@ -0,0 +1,500 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.core.cluster.ClusterException;
+import org.apache.jackrabbit.core.cluster.ClusterNode;
+import org.apache.jackrabbit.core.cluster.SimpleClusterContext;
+import org.apache.jackrabbit.core.cluster.UpdateEventChannel;
+import org.apache.jackrabbit.core.cluster.UpdateEventListener;
+import org.apache.jackrabbit.core.config.ClusterConfig;
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.journal.Journal;
+import org.apache.jackrabbit.core.journal.JournalFactory;
+import org.apache.jackrabbit.core.journal.MemoryJournal;
+import org.apache.jackrabbit.core.journal.MemoryJournal.MemoryRecord;
+import org.apache.jackrabbit.core.observation.EventState;
+import org.apache.jackrabbit.core.persistence.bundle.AbstractBundlePersistenceManager;
+import org.apache.jackrabbit.core.persistence.bundle.ConsistencyCheckerImpl;
+import org.apache.jackrabbit.core.persistence.check.ReportItem;
+import org.apache.jackrabbit.core.persistence.util.BLOBStore;
+import org.apache.jackrabbit.core.persistence.util.NodePropBundle;
+import org.apache.jackrabbit.core.state.ChangeLog;
+import org.apache.jackrabbit.core.state.ItemStateException;
+import org.apache.jackrabbit.core.state.NoSuchItemStateException;
+import org.apache.jackrabbit.core.state.NodeReferences;
+import org.apache.jackrabbit.spi.NameFactory;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
+import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
+
+import junit.framework.TestCase;
+
+public class ConsistencyCheckerImplTest extends TestCase {
+
+    private static final NameFactory nameFactory = NameFactoryImpl.getInstance();
+
+    /** Default sync delay: 5 seconds. */
+    private static final long SYNC_DELAY = 5000;
+
+    private List<MemoryRecord> records = new ArrayList<MemoryRecord>();
+
+    private ClusterNode master;
+    private ClusterNode slave;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        master = createClusterNode("master");
+        master.start();
+
+        slave = createClusterNode("slave");
+        slave.start();
+    }
+
+
+    // Abandoned nodes are nodes that have a link to a parent but that
+    // parent does not have a link back to the child
+    public void testFixAbandonedNode() throws RepositoryException, ClusterException {
+        NodePropBundle bundle1 = new NodePropBundle(new NodeId(0, 0));
+        NodePropBundle bundle2 = new NodePropBundle(new NodeId(0, 1));
+
+        // node2 has a reference to node 1 as its parent, but node 1 doesn't have
+        // a corresponding child node entry
+        bundle2.setParentId(bundle1.getId());
+
+        MockPersistenceManager pm = new MockPersistenceManager(Arrays.asList(bundle1, bundle2));
+        ConsistencyCheckerImpl checker = new ConsistencyCheckerImpl(pm, null, null, master.createUpdateChannel("default"));
+
+        // set up cluster event update listener
+        final TestUpdateEventListener listener = new TestUpdateEventListener();
+        final UpdateEventChannel slaveEventChannel = slave.createUpdateChannel("default");
+        slaveEventChannel.setListener(listener);
+
+        checker.check(null, false);
+
+        Set<ReportItem> reportItems = checker.getReport().getItems();
+        assertEquals(1, reportItems.size());
+        ReportItem reportItem = reportItems.iterator().next();
+        assertEquals(ReportItem.Type.ABANDONED, reportItem.getType());
+        assertEquals(bundle2.getId().toString(), reportItem.getNodeId());
+
+        checker.repair();
+
+        // node1 should now have a child node entry for node2
+        bundle1 = pm.loadBundle(bundle1.getId());
+        assertEquals(1, bundle1.getChildNodeEntries().size());
+        assertEquals(bundle2.getId(), bundle1.getChildNodeEntries().get(0).getId());
+
+        slave.sync();
+
+        // verify events were correctly broadcast to cluster
+        assertNotNull("Cluster node did not receive update event", listener.changes);
+        assertTrue("Expected node1 to be modified", listener.changes.isModified(bundle1.getId()));
+    }
+
+    public void testDoubleCheckAbandonedNode() throws RepositoryException {
+        NodePropBundle bundle1 = new NodePropBundle(new NodeId(0, 0));
+        NodePropBundle bundle2 = new NodePropBundle(new NodeId(0, 1));
+
+        // node2 has a reference to node 1 as its parent, but node 1 doesn't have
+        // a corresponding child node entry
+        bundle2.setParentId(bundle1.getId());
+
+        MockPersistenceManager pm = new MockPersistenceManager(Arrays.asList(bundle1, bundle2));
+        ConsistencyCheckerImpl checker = new ConsistencyCheckerImpl(pm, null, null, null);
+
+        checker.check(null, false);
+
+        Set<ReportItem> reportItems = checker.getReport().getItems();
+        assertEquals(1, reportItems.size());
+        ReportItem reportItem = reportItems.iterator().next();
+        assertEquals(ReportItem.Type.ABANDONED, reportItem.getType());
+        assertEquals(bundle2.getId().toString(), reportItem.getNodeId());
+
+        checker.doubleCheckErrors();
+
+        assertFalse("Double check removed valid error", checker.getReport().getItems().isEmpty());
+
+        // fix the error
+        bundle1.addChildNodeEntry(nameFactory.create("", "test"), bundle2.getId());
+
+        checker.doubleCheckErrors();
+
+        assertTrue("Double check didn't remove invalid error", checker.getReport().getItems().isEmpty());
+    }
+
+    /*
+     * There was a bug where when there were multiple abandoned nodes by the same parent
+     * only one of them was fixed. Hence this separate test case for this scenario.
+     */
+    public void testFixMultipleAbandonedNodesBySameParent() throws RepositoryException {
+        NodePropBundle bundle1 = new NodePropBundle(new NodeId(0, 0));
+        NodePropBundle bundle2 = new NodePropBundle(new NodeId(0, 1));
+        NodePropBundle bundle3 = new NodePropBundle(new NodeId(1, 0));
+
+        // node2 and node3 have a reference to node1 as its parent, but node1 doesn't have
+        // corresponding child node entries
+        bundle2.setParentId(bundle1.getId());
+        bundle3.setParentId(bundle1.getId());
+
+        MockPersistenceManager pm = new MockPersistenceManager(Arrays.asList(bundle1, bundle2, bundle3));
+        ConsistencyCheckerImpl checker = new ConsistencyCheckerImpl(pm, null, null, null);
+
+        checker.check(null, false);
+        checker.repair();
+
+        // node1 should now have child node entries for node2 and node3
+        bundle1 = pm.loadBundle(bundle1.getId());
+        assertEquals(2, bundle1.getChildNodeEntries().size());
+        assertEquals(bundle2.getId(), bundle1.getChildNodeEntries().get(0).getId());
+        assertEquals(bundle3.getId(), bundle1.getChildNodeEntries().get(1).getId());
+    }
+
+    // Orphaned nodes are those nodes who's parent does not exist
+    public void testAddOrphanedNodeToLostAndFound() throws RepositoryException, ClusterException {
+        final NodeId lostAndFoundId = new NodeId(0, 0);
+        NodePropBundle lostAndFound = new NodePropBundle(lostAndFoundId);
+        // lost and found must be of type nt:unstructured
+        lostAndFound.setNodeTypeName(NameConstants.NT_UNSTRUCTURED);
+
+        final NodeId orphanedId = new NodeId(0, 1);
+        NodePropBundle orphaned = new NodePropBundle(orphanedId);
+        // set non-existent parent node id
+        orphaned.setParentId(new NodeId(1, 0));
+
+        MockPersistenceManager pm = new MockPersistenceManager(Arrays.asList(lostAndFound, orphaned));
+        ConsistencyCheckerImpl checker = new ConsistencyCheckerImpl(pm, null, lostAndFoundId.toString(),
+                master.createUpdateChannel("default"));
+
+        // set up cluster event update listener
+        final TestUpdateEventListener listener = new TestUpdateEventListener();
+        final UpdateEventChannel slaveEventChannel = slave.createUpdateChannel("default");
+        slaveEventChannel.setListener(listener);
+
+        checker.check(null, false);
+
+        Set<ReportItem> reportItems = checker.getReport().getItems();
+        assertEquals(1, reportItems.size());
+        ReportItem reportItem = reportItems.iterator().next();
+        assertEquals(ReportItem.Type.ORPHANED, reportItem.getType());
+        assertEquals(orphanedId.toString(), reportItem.getNodeId());
+
+        checker.repair();
+
+        // orphan should have been added to lost+found
+        lostAndFound = pm.loadBundle(lostAndFoundId);
+        assertEquals(1, lostAndFound.getChildNodeEntries().size());
+        assertEquals(orphanedId, lostAndFound.getChildNodeEntries().get(0).getId());
+
+        orphaned = pm.loadBundle(orphanedId);
+        assertEquals(lostAndFoundId, orphaned.getParentId());
+
+        slave.sync();
+
+        // verify events were correctly broadcast to cluster
+        assertNotNull("Cluster node did not receive update event", listener.changes);
+        assertTrue("Expected lostAndFound to be modified", listener.changes.isModified(lostAndFoundId));
+        assertTrue("Expected orphan to be modified", listener.changes.isModified(orphanedId));
+    }
+
+    public void testDoubleCheckOrphanedNode() throws RepositoryException {
+        NodePropBundle orphaned = new NodePropBundle(new NodeId(0, 1));
+        orphaned.setParentId(new NodeId(1, 0));
+
+        MockPersistenceManager pm = new MockPersistenceManager(Arrays.asList(orphaned));
+        ConsistencyCheckerImpl checker = new ConsistencyCheckerImpl(pm, null, null, null);
+
+        checker.check(null, false);
+
+        Set<ReportItem> reportItems = checker.getReport().getItems();
+        assertEquals(1, reportItems.size());
+        ReportItem reportItem = reportItems.iterator().next();
+        assertEquals(ReportItem.Type.ORPHANED, reportItem.getType());
+        assertEquals(orphaned.getId().toString(), reportItem.getNodeId());
+
+        checker.doubleCheckErrors();
+
+        assertFalse("Double check removed valid error", checker.getReport().getItems().isEmpty());
+
+        // fix the error
+        NodePropBundle parent = new NodePropBundle(orphaned.getParentId());
+        pm.bundles.put(parent.getId(), parent);
+
+        checker.doubleCheckErrors();
+
+        assertTrue("Double check didn't remove invalid error", checker.getReport().getItems().isEmpty());
+    }
+
+    // Disconnected nodes are those nodes for which there are nodes
+    // that have the node as its child, but the node itself does not
+    // have those nodes as its parent
+    public void testFixDisconnectedNode() throws RepositoryException, ClusterException {
+        NodePropBundle bundle1 = new NodePropBundle(new NodeId(0, 0));
+        NodePropBundle bundle2 = new NodePropBundle(new NodeId(0, 1));
+        NodePropBundle bundle3 = new NodePropBundle(new NodeId(1, 0));
+
+        // node1 has child node3
+        bundle1.addChildNodeEntry(nameFactory.create("", "test"), bundle3.getId());
+        // node2 also has child node3
+        bundle2.addChildNodeEntry(nameFactory.create("", "test"), bundle3.getId());
+        // node3 has node2 as parent
+        bundle3.setParentId(bundle2.getId());
+
+        MockPersistenceManager pm = new MockPersistenceManager(Arrays.asList(bundle1, bundle2, bundle3));
+        ConsistencyCheckerImpl checker = new ConsistencyCheckerImpl(pm, null, null, master.createUpdateChannel("default"));
+
+        // set up cluster event update listener
+        final TestUpdateEventListener listener = new TestUpdateEventListener();
+        final UpdateEventChannel slaveEventChannel = slave.createUpdateChannel("default");
+        slaveEventChannel.setListener(listener);
+
+        checker.check(null, false);
+
+        Set<ReportItem> reportItems = checker.getReport().getItems();
+        assertEquals(1, reportItems.size());
+        ReportItem reportItem = reportItems.iterator().next();
+        assertEquals(ReportItem.Type.DISCONNECTED, reportItem.getType());
+        assertEquals(bundle1.getId().toString(), reportItem.getNodeId());
+
+        checker.repair();
+
+        bundle1 = pm.loadBundle(bundle1.getId());
+        bundle2 = pm.loadBundle(bundle2.getId());
+        bundle3 = pm.loadBundle(bundle3.getId());
+
+        // node3 should have been removed as child node entry of node1
+        assertEquals(0, bundle1.getChildNodeEntries().size());
+
+        // node3 should still be a child of node2
+        assertEquals(1, bundle2.getChildNodeEntries().size());
+        assertEquals(bundle2.getId(), bundle3.getParentId());
+
+        slave.sync();
+
+        // verify events were correctly broadcast to cluster
+        assertNotNull("Cluster node did not receive update event", listener.changes);
+        assertTrue("Expected node1 to be modified", listener.changes.isModified(bundle1.getId()));
+    }
+
+    public void testDoubleCheckDisonnectedNode() throws RepositoryException {
+        NodePropBundle bundle1 = new NodePropBundle(new NodeId(0, 0));
+        NodePropBundle bundle2 = new NodePropBundle(new NodeId(0, 1));
+        NodePropBundle bundle3 = new NodePropBundle(new NodeId(1, 0));
+
+        // node1 has child node3
+        bundle1.addChildNodeEntry(nameFactory.create("", "test"), bundle3.getId());
+        // node2 also has child node3
+        bundle2.addChildNodeEntry(nameFactory.create("", "test"), bundle3.getId());
+        // node3 has node2 as parent
+        bundle3.setParentId(bundle2.getId());
+
+        MockPersistenceManager pm = new MockPersistenceManager(Arrays.asList(bundle1, bundle2, bundle3));
+        ConsistencyCheckerImpl checker = new ConsistencyCheckerImpl(pm, null, null, null);
+
+        checker.check(null, false);
+
+        Set<ReportItem> reportItems = checker.getReport().getItems();
+        assertEquals(1, reportItems.size());
+        ReportItem reportItem = reportItems.iterator().next();
+        assertEquals(ReportItem.Type.DISCONNECTED, reportItem.getType());
+        assertEquals(bundle1.getId().toString(), reportItem.getNodeId());
+
+        checker.doubleCheckErrors();
+
+        assertFalse("Double check removed valid error", checker.getReport().getItems().isEmpty());
+
+        // fix the error
+        bundle1.getChildNodeEntries().remove(0);
+
+        checker.doubleCheckErrors();
+
+        assertTrue("Double check didn't remove invalid error", checker.getReport().getItems().isEmpty());
+    }
+
+    public void testFixMissingNode() throws RepositoryException, ClusterException {
+        NodePropBundle bundle = new NodePropBundle(new NodeId(0, 0));
+        bundle.addChildNodeEntry(nameFactory.create("", "test"), new NodeId(0, 1));
+
+        MockPersistenceManager pm = new MockPersistenceManager(Arrays.asList(bundle));
+
+        ConsistencyCheckerImpl checker = new ConsistencyCheckerImpl(pm, null, null, master.createUpdateChannel("default"));
+
+        // set up cluster event update listener
+        final TestUpdateEventListener listener = new TestUpdateEventListener();
+        final UpdateEventChannel slaveEventChannel = slave.createUpdateChannel("default");
+        slaveEventChannel.setListener(listener);
+
+        checker.check(null, false);
+
+        Set<ReportItem> reportItems = checker.getReport().getItems();
+        assertEquals(1, reportItems.size());
+        ReportItem reportItem = reportItems.iterator().next();
+        assertEquals(ReportItem.Type.MISSING, reportItem.getType());
+        assertEquals(bundle.getId().toString(), reportItem.getNodeId());
+
+        checker.repair();
+
+        // node should have no child no entries
+        assertTrue(bundle.getChildNodeEntries().isEmpty());
+
+        slave.sync();
+
+        // verify events were correctly broadcast to cluster
+        assertNotNull("Cluster node did not receive update event", listener.changes);
+        assertTrue("Expected node to be modified", listener.changes.isModified(bundle.getId()));
+    }
+
+    public void testDoubleCheckMissingNode() throws RepositoryException {
+        NodePropBundle bundle = new NodePropBundle(new NodeId(0, 0));
+        final NodeId childNodeId = new NodeId(0, 1);
+        bundle.addChildNodeEntry(nameFactory.create("", "test"), childNodeId);
+
+        MockPersistenceManager pm = new MockPersistenceManager(Arrays.asList(bundle));
+
+        ConsistencyCheckerImpl checker = new ConsistencyCheckerImpl(pm, null, null, null);
+
+        checker.check(null, false);
+
+        Set<ReportItem> reportItems = checker.getReport().getItems();
+        assertEquals(1, reportItems.size());
+        ReportItem reportItem = reportItems.iterator().next();
+        assertEquals(ReportItem.Type.MISSING, reportItem.getType());
+        assertEquals(bundle.getId().toString(), reportItem.getNodeId());
+
+        checker.doubleCheckErrors();
+
+        assertFalse("Double check removed valid error", checker.getReport().getItems().isEmpty());
+
+        // fix the error
+        NodePropBundle child = new NodePropBundle(childNodeId);
+        pm.bundles.put(childNodeId, child);
+
+        checker.doubleCheckErrors();
+
+        assertTrue("Double check didn't remove invalid error", checker.getReport().getItems().isEmpty());
+
+    }
+
+    private ClusterNode createClusterNode(String id) throws Exception {
+        final MemoryJournal journal = new MemoryJournal() {
+            protected boolean syncAgainOnNewRecords() {
+                return true;
+            }
+        };
+        JournalFactory jf = new JournalFactory() {
+            public Journal getJournal(NamespaceResolver resolver)
+                    throws RepositoryException {
+                return journal;
+            }
+        };
+        ClusterConfig cc = new ClusterConfig(id, SYNC_DELAY, jf);
+        SimpleClusterContext context = new SimpleClusterContext(cc);
+
+        journal.setRepositoryHome(context.getRepositoryHome());
+        journal.init(id, context.getNamespaceResolver());
+        journal.setRecords(records);
+
+        ClusterNode clusterNode = new ClusterNode();
+        clusterNode.init(context);
+        return clusterNode;
+    }
+
+    private static class MockPersistenceManager extends AbstractBundlePersistenceManager {
+
+        private Map<NodeId, NodePropBundle> bundles = new LinkedHashMap<NodeId, NodePropBundle>();
+
+        private MockPersistenceManager(List<NodePropBundle> bundles) {
+            for (NodePropBundle bundle : bundles) {
+                this.bundles.put(bundle.getId(), bundle);
+            }
+        }
+
+        public List<NodeId> getAllNodeIds(final NodeId after, final int maxCount) throws ItemStateException, RepositoryException {
+            List<NodeId> allNodeIds = new ArrayList<NodeId>();
+            boolean add = after == null;
+            for (NodeId nodeId : bundles.keySet()) {
+                if (add) {
+                    allNodeIds.add(nodeId);
+                }
+                if (!add) {
+                    add = nodeId.equals(after);
+                }
+            }
+            return allNodeIds;
+        }
+
+        @Override
+        protected NodePropBundle loadBundle(final NodeId id) {
+            return bundles.get(id);
+        }
+
+        @Override
+        protected void evictBundle(final NodeId id) {
+        }
+
+        @Override
+        protected void storeBundle(final NodePropBundle bundle) throws ItemStateException {
+            bundles.put(bundle.getId(), bundle);
+        }
+
+        @Override
+        protected void destroyBundle(final NodePropBundle bundle) throws ItemStateException {
+            bundles.remove(bundle.getId());
+        }
+
+        @Override
+        protected void destroy(final NodeReferences refs) throws ItemStateException {
+        }
+
+        @Override
+        protected void store(final NodeReferences refs) throws ItemStateException {
+        }
+
+        @Override
+        protected BLOBStore getBlobStore() {
+            return null;
+        }
+
+        public NodeReferences loadReferencesTo(final NodeId id) throws NoSuchItemStateException, ItemStateException {
+            return null;
+        }
+
+        public boolean existsReferencesTo(final NodeId targetId) throws ItemStateException {
+            return false;
+        }
+    }
+
+    private static class TestUpdateEventListener implements UpdateEventListener {
+
+        private ChangeLog changes;
+
+        @Override
+        public void externalUpdate(final ChangeLog changes, final List<EventState> events, final long timestamp, final String userData) throws RepositoryException {
+            this.changes = changes;
+        }
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/DataStoreTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/DataStoreTest.java
index 5df0a55..c7f7ea0 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/DataStoreTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/DataStoreTest.java
@@ -17,6 +17,7 @@
 package org.apache.jackrabbit.core.data;
 
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
 import org.apache.jackrabbit.core.data.db.DbDataStore;
 import org.apache.jackrabbit.test.JUnitTest;
 
@@ -102,6 +103,87 @@ public class DataStoreTest extends JUnitTest {
         }
     }
 
+    public void testDeleteRecordWithParentCollision() throws Exception {
+        FileDataStore fds = new FileDataStore();
+        fds.init(testDir + "/fileDeleteCollision");
+
+        String c1 = "06b2f82fd81b2c20";
+        String c2 = "02c60cb75083ceef";
+        DataRecord d1 = fds.addRecord(IOUtils.toInputStream(c1));
+        DataRecord d2 = fds.addRecord(IOUtils.toInputStream(c2));
+        fds.deleteRecord(d1.getIdentifier());
+        DataRecord testRecord = fds.getRecordIfStored(d2.getIdentifier());
+
+        assertNotNull(testRecord);
+        assertEquals(d2.getIdentifier(), testRecord.getIdentifier());
+        // Check the presence of the parent directory (relies on internal details of the FileDataStore)
+        File parentDirD1 = new File(
+            fds.getPath() + System.getProperty("file.separator") + d1.getIdentifier().toString().substring(0, 2));
+        assertTrue(parentDirD1.exists());
+    }
+
+    public void testDeleteRecordWithoutParentCollision() throws Exception {
+        FileDataStore fds = new FileDataStore();
+        fds.init(testDir + "/fileDelete");
+
+        String c1 = "idhfigjhehgkdfgk";
+        String c2 = "02c60cb75083ceef";
+        DataRecord d1 = fds.addRecord(IOUtils.toInputStream(c1));
+        DataRecord d2 = fds.addRecord(IOUtils.toInputStream(c2));
+        fds.deleteRecord(d1.getIdentifier());
+        DataRecord testRecord = fds.getRecordIfStored(d2.getIdentifier());
+
+        assertNotNull(testRecord);
+        assertEquals(d2.getIdentifier(), testRecord.getIdentifier());
+        // Check the absence of the parent directory (relies on internal details of the FileDataStore)
+        File parentDirD1 = new File(
+            fds.getPath() + System.getProperty("file.separator") + d1.getIdentifier().toString().substring(0, 2));
+        assertFalse(parentDirD1.exists());
+    }
+
+    public void testReference() throws Exception {
+        byte[] data = new byte[12345];
+        new Random(12345).nextBytes(data);
+        String reference;
+
+        FileDataStore store = new FileDataStore();
+        store.init(testDir + "/reference");
+        try {
+            DataRecord record = store.addRecord(new ByteArrayInputStream(data));
+            reference = record.getReference();
+
+            assertReference(data, reference, store);
+        } finally {
+            store.close();
+        }
+
+        store = new FileDataStore();
+        store.init(testDir + "/reference");
+        try {
+            assertReference(data, reference, store);
+        } finally {
+            store.close();
+        }
+    }
+
+    private void assertReference(
+            byte[] expected, String reference, DataStore store)
+            throws Exception {
+        DataRecord record = store.getRecordFromReference(reference);
+        assertNotNull(record);
+        assertEquals(expected.length, record.getLength());
+
+        InputStream stream = record.getStream();
+        try {
+            for (int i = 0; i < expected.length; i++) {
+                assertEquals(expected[i] & 0xff, stream.read());
+            }
+            assertEquals(-1, stream.read());
+        } finally {
+            stream.close();
+        }
+    }
+
     private void shutdownDatabase(String url) {
         if (url.startsWith("jdbc:derby:") || url.startsWith("jdbc:hsqldb:")) {
             try {
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/GCEventListenerTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/GCEventListenerTest.java
index 28613fa..016a51a 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/GCEventListenerTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/GCEventListenerTest.java
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.core.data;
 import org.apache.jackrabbit.api.management.DataStoreGarbageCollector;
 import org.apache.jackrabbit.api.management.MarkEventListener;
 import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.gc.GarbageCollector;
 import org.apache.jackrabbit.test.AbstractJCRTest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -110,9 +111,6 @@ public class GCEventListenerTest extends AbstractJCRTest implements MarkEventLis
         }
     }
 
-    public void afterScanning(Node n) throws RepositoryException {
-    }
-
     public void beforeScanning(Node n) throws RepositoryException {
         String s = getNodeName(n);
         if (s != null) {
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/GCSubtreeMoveTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/GCSubtreeMoveTest.java
new file mode 100644
index 0000000..97a3836
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/GCSubtreeMoveTest.java
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Properties;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.ValueFactory;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.api.JackrabbitRepository;
+import org.apache.jackrabbit.api.JackrabbitRepositoryFactory;
+import org.apache.jackrabbit.api.management.MarkEventListener;
+import org.apache.jackrabbit.core.RepositoryFactoryImpl;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.gc.GarbageCollector;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test case for the scenario where the GC thread traverses the workspace and at
+ * some point, a subtree that the GC thread did not see yet is moved to a location
+ * that the thread has already traversed. The GC thread should not ignore binaries 
+ * references by this subtree and eventually delete them.
+ */
+public class GCSubtreeMoveTest extends TestCase {
+
+    private static final Logger logger = LoggerFactory.getLogger(GCSubtreeMoveTest.class);
+
+    private String testDirectory;
+    private JackrabbitRepository repository;
+    private Session sessionGarbageCollector;
+    private Session sessionMover;
+
+    public void setUp() throws IOException {
+        testDirectory = "target/" + getClass().getSimpleName()  + "/" + getName();
+        FileUtils.deleteDirectory(new File(testDirectory));
+    }
+
+    public void tearDown() throws IOException {
+        sessionGarbageCollector.logout();
+        sessionMover.logout();
+        repository.shutdown();
+
+        repository = null;
+        sessionGarbageCollector = null;
+        sessionMover = null;
+
+        FileUtils.deleteDirectory(new File(testDirectory));
+        testDirectory = null;
+    }
+
+    public void test() {
+        setupRepository();
+
+        GarbageCollector garbageCollector = setupGarbageCollector();
+        // To make sure even listener for NODE_ADDED is registered in GC.
+        garbageCollector.setPersistenceManagerScan(false);
+
+        assertEquals(0, getBinaryCount(garbageCollector));
+        setupNodes();
+        assertEquals(1, getBinaryCount(garbageCollector));
+        garbageCollector.getDataStore().clearInUse();
+
+        garbageCollector.setMarkEventListener(new MarkEventListener() {
+
+            public void beforeScanning(Node node) throws RepositoryException {
+                String path = node.getPath();
+                if (path.startsWith("/node")) {
+                    log("Traversing: " + node.getPath());
+                }
+
+                if ("/node1".equals(node.getPath())) {
+                    String from = "/node2/node3";
+                    String to = "/node0/node3";
+                    log("Moving " + from + " -> " + to);
+                    sessionMover.move(from, to);
+                    sessionMover.save();
+                    sleepForFile();
+                }
+            }
+        });
+
+        try {
+            garbageCollector.getDataStore().clearInUse();
+            garbageCollector.mark();
+            garbageCollector.stopScan();
+            sleepForFile();
+            int numberOfDeleted = garbageCollector.sweep();
+            log("Number of deleted: " + numberOfDeleted);
+            // Binary data should still be there.
+            assertEquals(1, getBinaryCount(garbageCollector));
+        } catch (RepositoryException e) {
+            e.printStackTrace();
+            failWithException(e);
+        } finally {
+            garbageCollector.close();
+        }
+    }
+
+    private void setupNodes() {
+        try {
+            Node rootNode = sessionMover.getRootNode();
+            rootNode.addNode("node0");
+            rootNode.addNode("node1");
+            Node node2 = rootNode.addNode("node2");
+            Node node3 = node2.addNode("node3");
+            Node nodeWithBinary = node3.addNode("node-with-binary");
+            ValueFactory vf = sessionGarbageCollector.getValueFactory();
+            nodeWithBinary.setProperty("prop", vf.createBinary(new RandomInputStream(10, 1000)));
+            sessionMover.save();
+            sleepForFile();
+        } catch (RepositoryException e) {
+            failWithException(e);
+        }
+    }
+
+    private void sleepForFile() {
+        // Make sure the file is old (access time resolution is 2 seconds)
+        try {
+            Thread.sleep(2200);
+        } catch (InterruptedException ignore) {
+        }
+    }
+
+    private void setupRepository() {
+        JackrabbitRepositoryFactory repositoryFactory = new RepositoryFactoryImpl();
+        createRepository(repositoryFactory);
+        login();
+    }
+
+    private void createRepository(JackrabbitRepositoryFactory repositoryFactory) {
+        Properties prop = new Properties();
+        prop.setProperty("org.apache.jackrabbit.repository.home", testDirectory);
+        prop.setProperty("org.apache.jackrabbit.repository.conf", testDirectory + "/repository.xml");
+        try {
+            repository = (JackrabbitRepository)repositoryFactory.getRepository(prop);
+        } catch (RepositoryException e) {
+            failWithException(e);
+        };
+    }
+
+    private void login() {
+        try {
+            sessionGarbageCollector = repository.login(new SimpleCredentials("admin", "admin".toCharArray()));
+            sessionMover = repository.login(new SimpleCredentials("admin", "admin".toCharArray()));
+        } catch (Exception e) {
+            failWithException(e);
+        }
+    }
+
+    private GarbageCollector setupGarbageCollector() {
+        try {
+            return ((SessionImpl) sessionGarbageCollector).createDataStoreGarbageCollector();
+        } catch (RepositoryException e) {
+            failWithException(e);
+        }
+        return null;
+    }
+
+    private void failWithException(Exception e) {
+        fail("Not expected: " + e.getMessage());
+    }
+
+    private int getBinaryCount(GarbageCollector garbageCollector) {
+        int count = 0;
+        Iterator<DataIdentifier> it;
+        try {
+            it = garbageCollector.getDataStore().getAllIdentifiers();
+            while (it.hasNext()) {
+                it.next();
+                count++;
+            }
+        } catch (DataStoreException e) {
+            failWithException(e);
+        }
+        log("Binary count: " + count);
+        return count;
+    }
+
+    private void log(String message) {
+        logger.debug(message);
+        //System.out.println(message);
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/GCThread.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/GCThread.java
index 8c36c4f..0b4a047 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/GCThread.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/GCThread.java
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.core.data;
 import org.apache.jackrabbit.api.management.DataStoreGarbageCollector;
 import org.apache.jackrabbit.api.management.MarkEventListener;
 import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.gc.GarbageCollector;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/GarbageCollectorTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/GarbageCollectorTest.java
index 520a89a..1880129 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/GarbageCollectorTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/GarbageCollectorTest.java
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.core.data;
 import org.apache.jackrabbit.api.management.DataStoreGarbageCollector;
 import org.apache.jackrabbit.api.management.MarkEventListener;
 import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.gc.GarbageCollector;
 import org.apache.jackrabbit.test.AbstractJCRTest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -27,6 +28,7 @@ import EDU.oswego.cs.dl.util.concurrent.SynchronousChannel;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Iterator;
+import javax.jcr.Binary;
 import javax.jcr.Credentials;
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
@@ -46,6 +48,7 @@ public class GarbageCollectorTest extends AbstractJCRTest implements ScanEventLi
         final Session session = getHelper().getReadWriteSession();
 
         final DataStoreGarbageCollector gc = ((SessionImpl) session).createDataStoreGarbageCollector();
+        gc.setPersistenceManagerScan(false);
         final Exception[] ex = new Exception[1];
         gc.setMarkEventListener(new MarkEventListener() {
             boolean closed;
@@ -54,7 +57,7 @@ public class GarbageCollectorTest extends AbstractJCRTest implements ScanEventLi
                 closeTest();
             }
 
-            private void closeTest() throws RepositoryException {
+            private void closeTest() {
                 if (closed) {
                     ex[0] = new Exception("Scanning after the session is closed");
                 }
@@ -114,6 +117,7 @@ public class GarbageCollectorTest extends AbstractJCRTest implements ScanEventLi
         }.start();
         assertEquals("x", sync.take());
         DataStoreGarbageCollector gc = ((SessionImpl) session).createDataStoreGarbageCollector();
+        gc.setPersistenceManagerScan(false);
         gc.mark();
         gc.sweep();
         sync.put("deleted");
@@ -137,9 +141,11 @@ public class GarbageCollectorTest extends AbstractJCRTest implements ScanEventLi
 
         root.addNode("node1");
         Node node2 = root.addNode("node2");
-        Node n = node2.addNode("nodeWithBlob");
+        Node n = node2.addNode("nodeWithBlob").addNode("sub");
         ValueFactory vf = session.getValueFactory();
-        n.setProperty("test", vf.createBinary(new RandomInputStream(10, 1000)));
+        Binary b = vf.createBinary(new RandomInputStream(20, 1000));
+        n.setProperty("test", b);
+        session.save();
         n = node2.addNode("nodeWithTemporaryBlob");
         n.setProperty("test", vf.createBinary(new RandomInputStream(11, 1000)));
         session.save();
@@ -148,6 +154,9 @@ public class GarbageCollectorTest extends AbstractJCRTest implements ScanEventLi
         session.save();
 
         GarbageCollector gc = ((SessionImpl)session).createDataStoreGarbageCollector();
+        gc.getDataStore().clearInUse();
+        gc.setPersistenceManagerScan(false);
+        gc.setMarkEventListener(this);
 
         if (gc.getDataStore() instanceof FileDataStore) {
             // make sure the file is old (access time resolution is 2 seconds)
@@ -158,21 +167,58 @@ public class GarbageCollectorTest extends AbstractJCRTest implements ScanEventLi
         gc.mark();
         int count = listIdentifiers(gc);
         LOG.debug("stop scanning; currently " + count + " identifiers");
-        gc.stopScan();
         LOG.debug("deleting...");
         gc.getDataStore().clearInUse();
         assertTrue(gc.sweep() > 0);
         int count2 = listIdentifiers(gc);
         assertEquals(count - 1, count2);
 
+        // verify the node was moved, and that the binary is still there
+        n = root.getNode("node1").getNode("nodeWithBlob").getNode("sub");
+        b = n.getProperty("test").getValue().getBinary();
+        InputStream in = b.getStream();
+        InputStream in2 = new RandomInputStream(20, 1000);
+        verifyInputStream(in, in2);
+
         deleteMyNodes();
 
         gc.close();
     }
+    
+    /**
+     *  Test to validate that two  GC cannot run simulatenously. one 
+     *  exits throwing exception  
+     */
+    public void testSimulatenousRunGC() throws Exception {
+        Node root = testRootNode;
+        Session session = root.getSession();
+
+        GCThread gct1 = new GCThread(session);
+        GCThread gct2 = new GCThread(session);
+        Thread gcThread1 = new Thread(gct1, "Datastore Garbage Collector 1");
+        Thread gcThread2 = new Thread(gct2, "Datastore Garbage Collector 2");
+        // run simulatensou gc
+        gcThread1.start();
+        gcThread2.start();
+        Thread.sleep(100);
+        
+        gct1.setStop(true);
+        gct2.setStop(true);
+        
+        // allow them to complete
+        gcThread1.join();
+        gcThread2.join();
+
+        // only one should throw error
+        int count = (gct1.getException() == null ? 0 : 1) + (gct2.getException() == null ? 0 : 1);
+        assertEquals("only one gc should throw exception ", 1, count);
+    }
 
     private void runGC(Session session, boolean all) throws Exception {
         GarbageCollector gc = ((SessionImpl)session).createDataStoreGarbageCollector();
         gc.setMarkEventListener(this);
+        gc.setPersistenceManagerScan(false);
+
         if (gc.getDataStore() instanceof FileDataStore) {
             // make sure the file is old (access time resolution is 2 seconds)
             Thread.sleep(2000);
@@ -186,7 +232,7 @@ public class GarbageCollectorTest extends AbstractJCRTest implements ScanEventLi
         gc.close();
     }
 
-    private int listIdentifiers(GarbageCollector gc) throws DataStoreException {
+    private static int listIdentifiers(GarbageCollector gc) throws DataStoreException {
         LOG.debug("identifiers:");
         int count = 0;
         Iterator<DataIdentifier> it = gc.getDataStore().getAllIdentifiers();
@@ -219,6 +265,14 @@ public class GarbageCollectorTest extends AbstractJCRTest implements ScanEventLi
 
         InputStream in = n.getProperty("test").getBinary().getStream();
         InputStream in2 = new RandomInputStream(10, 1000);
+        verifyInputStream(in, in2);
+
+        deleteMyNodes();
+
+        s2.logout();
+    }
+
+    private static void verifyInputStream(InputStream in, InputStream in2) throws IOException {
         while (true) {
             int a = in.read();
             int b = in2.read();
@@ -228,9 +282,6 @@ public class GarbageCollectorTest extends AbstractJCRTest implements ScanEventLi
             }
         }
 
-        deleteMyNodes();
-
-        s2.logout();
     }
 
     public void afterScanning(Node n) throws RepositoryException {
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TempFileInputStreamTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TempFileInputStreamTest.java
deleted file mode 100644
index bf67c51..0000000
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TempFileInputStreamTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.data;
-
-import org.apache.jackrabbit.core.data.db.TempFileInputStream;
-import org.apache.jackrabbit.test.JUnitTest;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Tests the class TempFileInputStream
- */
-public class TempFileInputStreamTest extends JUnitTest {
-
-    public void testReadPastEOF() throws IOException {
-        File temp = File.createTempFile("test", null);
-        TempFileInputStream.writeToFileAndClose(new ByteArrayInputStream(new byte[1]), temp);
-        TempFileInputStream in = new TempFileInputStream(temp);
-        assertEquals(0, in.read());
-        assertEquals(-1, in.read());
-        assertEquals(-1, in.read());
-        assertEquals(-1, in.read());
-        in.close();
-    }
-
-    public void testMarkReset() throws IOException {
-        File temp = File.createTempFile("test", null);
-        TempFileInputStream.writeToFileAndClose(new ByteArrayInputStream(new byte[10]), temp);
-        InputStream in = new BufferedInputStream(new TempFileInputStream(temp));
-        in.mark(100);
-        for (int i = 0; i < 10; i++) {
-            assertEquals(0, in.read());
-        }
-        assertEquals(-1, in.read());
-        in.reset();
-        for (int i = 0; i < 10; i++) {
-            assertEquals(0, in.read());
-        }
-        assertEquals(-1, in.read());
-        in.close();
-    }
-
-}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestAll.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestAll.java
index e2d870e..8ac2b9f 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestAll.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/TestAll.java
@@ -48,9 +48,9 @@ public class TestAll extends TestCase {
         suite.addTestSuite(NodeTypeTest.class);
         suite.addTestSuite(OpenFilesTest.class);
         suite.addTestSuite(PersistenceManagerIteratorTest.class);
-        suite.addTestSuite(TempFileInputStreamTest.class);
         suite.addTestSuite(TestTwoGetStreams.class);
         suite.addTestSuite(WriteWhileReadingTest.class);
+        suite.addTestSuite(GCSubtreeMoveTest.class);
 
         return suite;
     }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/fs/TestAll.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/fs/TestAll.java
index 65fda95..79db04f 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/fs/TestAll.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/fs/TestAll.java
@@ -1,44 +1,44 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.fs;
-
-import org.apache.jackrabbit.core.fs.db.DerbyFileSystemTest;
-import org.apache.jackrabbit.core.fs.local.LocalFileSystemTest;
-import org.apache.jackrabbit.core.fs.mem.MemoryFileSystemTest;
-
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
-/**
- * Test suite that includes all test cases for this module.
- */
-public class TestAll extends TestCase {
-
-    /**
-     * Returns a test suite that executes all tests inside this package.
-     *
-     * @return a test suite that executes all tests inside this package
-     */
-    public static Test suite() {
-        TestSuite suite = new TestSuite("FileSystem tests");
-        suite.addTestSuite(DerbyFileSystemTest.class);
-        suite.addTestSuite(LocalFileSystemTest.class);
-        suite.addTestSuite(MemoryFileSystemTest.class);
-        return suite;
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.fs;
+
+import org.apache.jackrabbit.core.fs.db.DerbyFileSystemTest;
+import org.apache.jackrabbit.core.fs.local.LocalFileSystemTest;
+import org.apache.jackrabbit.core.fs.mem.MemoryFileSystemTest;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test suite that includes all test cases for this module.
+ */
+public class TestAll extends TestCase {
+
+    /**
+     * Returns a test suite that executes all tests inside this package.
+     *
+     * @return a test suite that executes all tests inside this package
+     */
+    public static Test suite() {
+        TestSuite suite = new TestSuite("FileSystem tests");
+        suite.addTestSuite(DerbyFileSystemTest.class);
+        suite.addTestSuite(LocalFileSystemTest.class);
+        suite.addTestSuite(MemoryFileSystemTest.class);
+        return suite;
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/id/NodeIdTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/id/NodeIdTest.java
index a981e59..17c8c71 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/id/NodeIdTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/id/NodeIdTest.java
@@ -1,106 +1,106 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.id;
-
-import junit.framework.TestCase;
-
-public class NodeIdTest extends TestCase {
-
-    private static final NodeId[] ids = {
-        NodeId.randomId(), // random id
-        new NodeId(0, 0),
-        new NodeId(-1, -1),
-        new NodeId("cafebabe-cafe-babe-cafe-babecafebabe")
-    };
-
-    public void testDenotesNode() {
-        for (NodeId id : ids) {
-            assertTrue(id.denotesNode());
-        }
-    }
-
-    public void testGetMostAndLeastSignificantBits() {
-        for (NodeId id : ids) {
-            long msb = id.getMostSignificantBits();
-            long lsb = id.getLeastSignificantBits();
-            assertEquals(id, new NodeId(msb, lsb));
-        }
-    }
-
-    public void testGetRawBytes() {
-        for (NodeId id : ids) {
-            assertEquals(id, new NodeId(id.getRawBytes()));
-        }
-    }
-
-    public void testToString() {
-        for (NodeId id : ids) {
-            assertEquals(id, new NodeId(id.toString()));
-        }
-    }
-
-    public void testCompareTo() {
-        for (NodeId id : ids) {
-            assertEquals(0, id.compareTo(id));
-        }
-
-        NodeId[] ordered = {
-                new NodeId(-1, -1),
-                new NodeId(-1, 0),
-                new NodeId(0, -1),
-                new NodeId(0, 0),
-                new NodeId(0, 1),
-                new NodeId(1, 0),
-                new NodeId(1, 1)
-        };
-        for (int i = 0; i < ordered.length; i++) {
-            for (int j = 0; j < i; j++) {
-                assertEquals(1, ordered[i].compareTo(ordered[j]));
-            }
-            assertEquals(0, ordered[i].compareTo(ordered[i]));
-            for (int j = i + 1; j < ordered.length; j++) {
-                assertEquals(-1, ordered[i].compareTo(ordered[j]));
-            }
-        }
-    }
-
-    public void testUuidFormat() {
-        long maxHigh = 0, maxLow = 0, minHigh = -1L, minLow = -1L;
-        for (int i = 0; i < 100; i++) {
-            NodeId id = NodeId.randomId();
-            assertUuidFormat(id);
-            maxHigh |= id.getMostSignificantBits();
-            maxLow |= id.getLeastSignificantBits();
-            minHigh &= id.getMostSignificantBits();
-            minLow &= id.getLeastSignificantBits();
-        }
-        NodeId max = new NodeId(maxHigh, maxLow);
-        assertEquals("ffffffff-ffff-4fff-bfff-ffffffffffff", max.toString());
-        NodeId min = new NodeId(minHigh, minLow);
-        assertEquals("00000000-0000-4000-8000-000000000000", min.toString());
-    }
-
-    private void assertUuidFormat(NodeId id) {
-        long high = id.getMostSignificantBits();
-        long low = id.getLeastSignificantBits();
-        long high2 = (high & (~0xf000L)) | 0x4000L; // version 4 (random)
-        assertEquals(high, high2);
-        long low2 = (low & 0x3fffffffffffffffL) | 0x8000000000000000L; // variant (Leach-Salz)
-        assertEquals(low, low2);
-    }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.id;
+
+import junit.framework.TestCase;
+
+public class NodeIdTest extends TestCase {
+
+    private static final NodeId[] ids = {
+        NodeId.randomId(), // random id
+        new NodeId(0, 0),
+        new NodeId(-1, -1),
+        new NodeId("cafebabe-cafe-babe-cafe-babecafebabe")
+    };
+
+    public void testDenotesNode() {
+        for (NodeId id : ids) {
+            assertTrue(id.denotesNode());
+        }
+    }
+
+    public void testGetMostAndLeastSignificantBits() {
+        for (NodeId id : ids) {
+            long msb = id.getMostSignificantBits();
+            long lsb = id.getLeastSignificantBits();
+            assertEquals(id, new NodeId(msb, lsb));
+        }
+    }
+
+    public void testGetRawBytes() {
+        for (NodeId id : ids) {
+            assertEquals(id, new NodeId(id.getRawBytes()));
+        }
+    }
+
+    public void testToString() {
+        for (NodeId id : ids) {
+            assertEquals(id, new NodeId(id.toString()));
+        }
+    }
+
+    public void testCompareTo() {
+        for (NodeId id : ids) {
+            assertEquals(0, id.compareTo(id));
+        }
+
+        NodeId[] ordered = {
+                new NodeId(-1, -1),
+                new NodeId(-1, 0),
+                new NodeId(0, -1),
+                new NodeId(0, 0),
+                new NodeId(0, 1),
+                new NodeId(1, 0),
+                new NodeId(1, 1)
+        };
+        for (int i = 0; i < ordered.length; i++) {
+            for (int j = 0; j < i; j++) {
+                assertEquals(1, ordered[i].compareTo(ordered[j]));
+            }
+            assertEquals(0, ordered[i].compareTo(ordered[i]));
+            for (int j = i + 1; j < ordered.length; j++) {
+                assertEquals(-1, ordered[i].compareTo(ordered[j]));
+            }
+        }
+    }
+
+    public void testUuidFormat() {
+        long maxHigh = 0, maxLow = 0, minHigh = -1L, minLow = -1L;
+        for (int i = 0; i < 100; i++) {
+            NodeId id = NodeId.randomId();
+            assertUuidFormat(id);
+            maxHigh |= id.getMostSignificantBits();
+            maxLow |= id.getLeastSignificantBits();
+            minHigh &= id.getMostSignificantBits();
+            minLow &= id.getLeastSignificantBits();
+        }
+        NodeId max = new NodeId(maxHigh, maxLow);
+        assertEquals("ffffffff-ffff-4fff-bfff-ffffffffffff", max.toString());
+        NodeId min = new NodeId(minHigh, minLow);
+        assertEquals("00000000-0000-4000-8000-000000000000", min.toString());
+    }
+
+    private void assertUuidFormat(NodeId id) {
+        long high = id.getMostSignificantBits();
+        long low = id.getLeastSignificantBits();
+        long high2 = (high & (~0xf000L)) | 0x4000L; // version 4 (random)
+        assertEquals(high, high2);
+        long low2 = (low & 0x3fffffffffffffffL) | 0x8000000000000000L; // variant (Leach-Salz)
+        assertEquals(low, low2);
+    }
+
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/id/TestAll.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/id/TestAll.java
index 0a9ff61..6966798 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/id/TestAll.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/id/TestAll.java
@@ -1,41 +1,41 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.id;
-
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestSuite;
-
-/**
- * Test suite that includes all test cases for the id module.
- */
-public class TestAll extends TestCase {
-
-    /**
-     * Returns a test suite that executes all tests inside this package.
-     *
-     * @return a test suite that executes all tests inside this package
-     */
-    public static Test suite() {
-        TestSuite suite = new TestSuite("Identifier tests");
-
-        suite.addTestSuite(NodeIdFactoryTest.class);
-        suite.addTestSuite(NodeIdTest.class);
-
-        return suite;
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.id;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test suite that includes all test cases for the id module.
+ */
+public class TestAll extends TestCase {
+
+    /**
+     * Returns a test suite that executes all tests inside this package.
+     *
+     * @return a test suite that executes all tests inside this package
+     */
+    public static Test suite() {
+        TestSuite suite = new TestSuite("Identifier tests");
+
+        suite.addTestSuite(NodeIdFactoryTest.class);
+        suite.addTestSuite(NodeIdTest.class);
+
+        return suite;
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/BackwardsCompatibilityTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/BackwardsCompatibilityTest.java
deleted file mode 100644
index 6789c9b..0000000
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/BackwardsCompatibilityTest.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.integration;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Enumeration;
-import java.util.Random;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-import javax.jcr.Node;
-import javax.jcr.PathNotFoundException;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-import javax.jcr.Value;
-import javax.jcr.ValueFormatException;
-import javax.jcr.version.Version;
-import javax.jcr.version.VersionHistory;
-
-import junit.framework.TestCase;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.IOUtils;
-import org.apache.jackrabbit.core.RepositoryImpl;
-import org.apache.jackrabbit.core.config.RepositoryConfig;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class BackwardsCompatibilityTest extends TestCase {
-
-    /**
-     * Logger instance
-     */
-    private static final Logger log =
-        LoggerFactory.getLogger(BackwardsCompatibilityTest.class);
-
-    public void testBackwardsCompatibility() throws Exception {
-        File target = new File("target/backwards-compatibility-test");
-        FileUtils.deleteDirectory(target);
-        target.mkdirs();
-
-        File source = new File("src/test/resources/compatibility.zip");
-        unpack(source, target);
-
-        for (File dir : target.listFiles()) {
-            if (dir.isDirectory()) {
-                log.info("Testing backwards compatibility with {}", dir);
-                assertRepository(dir);
-            }
-        }
-    }
-
-    private void assertRepository(File directory) throws Exception {
-        File configuration = new File(directory, "repository.xml");
-
-        try {
-            RepositoryConfig config = RepositoryConfig.create(
-                    configuration.getPath(), directory.getPath());
-            RepositoryImpl repository = RepositoryImpl.create(config);
-            try {
-                Session session = repository.login(
-                        new SimpleCredentials("admin", "admin".toCharArray()));
-                try {
-                    assertTestData(session);
-                } finally {
-                    session.logout();
-                }
-            } finally {
-                repository.shutdown();
-            }
-        } catch (RepositoryException e) {
-            String message = "Unable to access repository " + directory;
-            log.error(message, e);
-            fail(message);
-        }
-    }
-
-    private void assertTestData(Session session) throws Exception {
-        Node root = session.getRootNode();
-
-        assertTrue(root.hasNode("test"));
-        Node test = root.getNode("test");
-
-        Node versionable = assertVersionable(test);
-        assertProperties(test, versionable);
-        assertVersionableCopy(test, versionable);
-        assertLock(test);
-        assertUsers(session);
-    }
-
-    private Node assertVersionable(Node test) throws RepositoryException {
-        assertTrue(test.hasNode("versionable"));
-        Node versionable = test.getNode("versionable");
-        assertTrue(versionable.isNodeType("nt:myversionable"));
-        assertTrue(versionable.isNodeType("nt:unstructured"));
-        assertTrue(versionable.isNodeType("mix:versionable"));
-        assertFalse(versionable.isCheckedOut());
-
-        VersionHistory history = versionable.getVersionHistory();
-        Version versionB = versionable.getBaseVersion();
-        String[] labels = history.getVersionLabels(versionB);
-        assertEquals(1, labels.length);
-        assertEquals("labelB", labels[0]);
-        Version versionA = history.getVersionByLabel("labelA");
-        versionable.restore(versionA, true);
-        assertEquals("A", versionable.getProperty("foo").getString());
-        versionable.restore(versionB, true);
-        assertEquals("B", versionable.getProperty("foo").getString());
-        return versionable;
-    }
-
-    private void assertProperties(Node test, Node versionable)
-            throws RepositoryException, PathNotFoundException,
-            ValueFormatException, IOException {
-        assertTrue(test.hasNode("properties"));
-        Node properties = test.getNode("properties");
-        assertTrue(properties.isNodeType("nt:unstructured"));
-
-        assertEquals(true, properties.getProperty("boolean").getBoolean());
-        assertEquals(0.123456789, properties.getProperty("double").getDouble());
-        assertEquals(1234567890, properties.getProperty("long").getLong());
-        Node reference = properties.getProperty("reference").getNode();
-        assertTrue(reference.isSame(versionable));
-        assertEquals("test", properties.getProperty("string").getString());
-
-        Value[] multiple = properties.getProperty("multiple").getValues();
-        assertEquals(3, multiple.length);
-        assertEquals("a", multiple[0].getString());
-        assertEquals("b", multiple[1].getString());
-        assertEquals("c", multiple[2].getString());
-
-        Calendar calendar = properties.getProperty("date").getDate();
-        assertEquals(1234567890, calendar.getTimeInMillis());
-
-        InputStream stream = properties.getProperty("binary").getStream();
-        try {
-            byte[] binary = new byte[100 * 1000];
-            new Random(1234567890).nextBytes(binary);
-            assertTrue(Arrays.equals(binary, IOUtils.toByteArray(stream)));
-        } finally {
-            stream.close();
-        }
-    }
-
-    private void assertVersionableCopy(Node test, Node versionable)
-            throws RepositoryException, IOException {
-        test.getSession().getWorkspace().copy(
-                versionable.getPath(),
-                versionable.getPath() + "-copy");
-        Node copy = test.getNode(versionable.getName() + "-copy");
-        copy.remove();
-        test.save();
-    }
-
-    private void assertLock(Node test) throws RepositoryException {
-        Node lock = test.getNode("lock");
-        assertTrue(lock.isLocked());
-        assertTrue(lock.hasProperty("jcr:lockOwner"));
-    }
-
-    private void assertUsers(Session session) throws RepositoryException {
-    }
-
-    private void unpack(File archive, File dir) throws IOException {
-        ZipFile zip = new ZipFile(archive);
-        try {
-            Enumeration<? extends ZipEntry> entries = zip.entries();
-            while (entries.hasMoreElements()) {
-                ZipEntry entry = entries.nextElement();
-                if (entry.getName().startsWith("META-INF")) {
-                } else if (entry.isDirectory()) {
-                    new File(dir, entry.getName()).mkdirs();
-                } else {
-                    File file = new File(dir, entry.getName());
-                    file.getParentFile().mkdirs();
-                    InputStream input = zip.getInputStream(entry);
-                    try {
-                        OutputStream output = new FileOutputStream(file);
-                        try {
-                            IOUtils.copy(input, output);
-                        } finally {
-                            output.close();
-                        }
-                    } finally {
-                        input.close();
-                    }
-                }
-            }
-        } finally {
-            zip.close();
-        }
-    }
-
-}
-
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/CachingHierarchyManagerConsistencyTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/CachingHierarchyManagerConsistencyTest.java
new file mode 100644
index 0000000..be40d58
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/CachingHierarchyManagerConsistencyTest.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.integration;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.EventListener;
+
+import org.apache.jackrabbit.commons.JcrUtils;
+import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test case for JCR-3617.
+ */
+public class CachingHierarchyManagerConsistencyTest extends AbstractJCRTest {
+
+    private static final Logger log = LoggerFactory.getLogger(CachingHierarchyManagerConsistencyTest.class);
+
+    private static final int TEST_DURATION = 10; // seconds
+    private static final int NUM_LISTENERS = 10;
+    private static final int ALL_EVENTS = Event.NODE_ADDED | Event.NODE_MOVED | Event.NODE_REMOVED | Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED;
+    private static final String TEST_PATH = "/my/test/path";
+
+    public void testObservation() throws Exception {
+        final List<Exception> exceptions = new ArrayList<Exception>();
+        Thread writer = new Thread(new Runnable() {
+            public void run() {
+                try {
+                    long end = System.currentTimeMillis() + TEST_DURATION * 1000;
+                    Session s = getHelper().getSuperuserSession();
+                    try {
+                        log.info("Starting to replace nodes");
+                        int i = 0;
+                        while (System.currentTimeMillis() < end) {
+                            replaceNodes(s, i++);
+                        }
+                    } finally {
+                        s.logout();
+                    }
+                } catch (RepositoryException e) {
+                    exceptions.add(e);
+                }
+            }
+        });
+        List<EventListener> listeners = new ArrayList<EventListener>();
+        for (int i = 0; i < NUM_LISTENERS; i++) {
+            final Session session = getHelper().getSuperuserSession();
+            listeners.add(new EventListener() {
+                public void onEvent(EventIterator events) {
+                    while (events.hasNext()) {
+                        Event event = events.nextEvent();
+                        String path = "n/a";
+                        try {
+                            if (event.getType() == Event.NODE_ADDED
+                                    || event.getType() == Event.PROPERTY_ADDED) {
+                                path = event.getPath();
+                                session.getItem(path);
+                            }
+                        } catch (PathNotFoundException e) {
+                            // ignore
+                        } catch (RepositoryException e) {
+                            log.error(e.toString() + " Unable to get item with path: " + path);
+                            exceptions.add(e);
+                        }
+                    }
+                }
+            });
+        }
+        for (EventListener listener : listeners) {
+            superuser.getWorkspace().getObservationManager().addEventListener(
+                    listener, ALL_EVENTS, "/", true, null, null, false);
+        }
+
+        writer.start();
+        writer.join();
+
+        for (EventListener listener : listeners) {
+            superuser.getWorkspace().getObservationManager().removeEventListener(listener);
+        }
+
+        log.info("" + exceptions.size() + " exception(s) occurred.");
+        if (!exceptions.isEmpty()) {
+            throw exceptions.get(0);
+        }
+    }
+
+    private void replaceNodes(Session session, int i) throws RepositoryException {
+        String nodeName = "node-" + (i % 100);
+        Node root = JcrUtils.getOrCreateByPath(testRoot + TEST_PATH, ntUnstructured, session);
+        String uuid = UUID.randomUUID().toString();
+        if (root.hasNode(nodeName)) {
+            Node n = root.getNode(nodeName);
+            uuid = n.getIdentifier();
+            n.remove();
+        }
+        Node n = ((NodeImpl) root).addNodeWithUuid(nodeName, ntUnstructured, uuid);
+        n.addMixin("mix:referenceable");
+        n.addNode("foo").addNode("bar");
+        n.addNode("qux");
+        session.save();
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/InterruptedQueryTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/InterruptedQueryTest.java
new file mode 100644
index 0000000..b197fe8
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/InterruptedQueryTest.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.integration;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.apache.lucene.util.Constants;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @see <a href="https://issues.apache.org/jira/browse/JCR-3469">JCR-3469</a>
+ */
+public class InterruptedQueryTest {
+
+    private RepositoryImpl repo;
+
+    private Session session;
+    
+    @Before
+    public void setUp() throws Exception {
+        if (Constants.WINDOWS) {
+            return;
+        }
+        deleteAll();
+        FileUtils.copyInputStreamToFile(
+                getClass().getResourceAsStream("repository-with-SimpleFSDirectory.xml"),
+                new File(getTestDir(), "repository.xml"));
+        repo = RepositoryImpl.create(RepositoryConfig.create(getTestDir()));
+        session = repo.login(new SimpleCredentials("admin", "admin".toCharArray()));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (session != null) {
+            session.logout();
+        }
+        if (repo != null) {
+            repo.shutdown();
+        }
+        deleteAll();
+    }
+
+    @Test
+    public void testQuery() throws Exception {
+        if (Constants.WINDOWS) {
+            return;
+        }
+        for (int i = 0; i < 100; i++) {
+            session.getRootNode().addNode("node" + i, "nt:unstructured");
+        }
+        session.save();
+        final QueryManager qm = session.getWorkspace().getQueryManager();
+        final AtomicBoolean stop = new AtomicBoolean(false);
+        final List<Exception> exceptions = Collections.synchronizedList(
+                new ArrayList<Exception>());
+        Thread t = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                while (!stop.get() && exceptions.isEmpty()) {
+                    try {
+                        // execute query
+                        String stmt = "//*[@jcr:primaryType='nt:unstructured']";
+                        qm.createQuery(stmt, Query.XPATH).execute();
+                    } catch (RepositoryException e) {
+                        if (Constants.SUN_OS) {
+                            // on Solaris it's OK when the root cause
+                            // of the exception is an InterruptedIOException
+                            // the underlying file is not closed
+                            Throwable t = e;
+                            while (t.getCause() != null) {
+                                t = t.getCause();
+                            }
+                            if (!(t instanceof InterruptedIOException)) {
+                                exceptions.add(e);
+                            }
+                        } else {
+                            exceptions.add(e);
+                        }
+                    }
+                }
+            }
+        });
+        t.start();
+        for (int i = 0; i < 200 && t.isAlive(); i++) {
+            t.interrupt();
+            Thread.sleep((long) (100.0 * Math.random())); 
+        }
+        stop.set(true);
+        t.join();
+        if (!exceptions.isEmpty()) {
+            throw exceptions.get(0);
+        }
+    }
+
+    private static void deleteAll() throws IOException {
+        FileUtils.deleteDirectory(getTestDir());
+    }
+
+    private static File getTestDir() throws IOException {
+        return new File("target",
+                InterruptedQueryTest.class.getSimpleName());
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/NodeImplTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/NodeImplTest.java
index e59cfc5..1e42128 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/NodeImplTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/NodeImplTest.java
@@ -1,93 +1,93 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.integration;
-
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-import javax.jcr.version.Version;
-
-import org.apache.jackrabbit.test.AbstractJCRTest;
-
-/**
- * Integration tests for the Node implementation in Jackrabbit core.
- */
-public class NodeImplTest extends AbstractJCRTest {
-
-    private Node node;
-
-    protected void setUp() throws Exception {
-        super.setUp();
-        node = testRootNode.addNode("testNodeImpl", "nt:unstructured");
-        testRootNode.save();
-    }
-
-    protected void tearDown() throws Exception {
-        node.remove();
-        testRootNode.save();
-        super.tearDown();
-    }
-
-    /**
-     * Test case for JCR-1389.
-     * 
-     * @see <a href="https://issues.apache.org/jira/browse/JCR-1389">JCR-1389</a>
-     */
-    public void testSetEmptyMultiValueProperty() throws RepositoryException {
-        Property property =
-            node.setProperty("test", new Value[0], PropertyType.LONG);
-        assertEquals(
-                "JCR-1389: setProperty(name, new Value[0], PropertyType.LONG)"
-                + " loses property type",
-                PropertyType.LONG, property.getType());
-    }
-
-    /**
-     * Test case for JCR-1227.
-     * 
-     * @see <a href="https://issues.apache.org/jira/browse/JCR-1227">JCR-1227</a>
-     */
-    public void testRestoreEmptyMultiValueProperty() throws Exception {
-        node.addMixin("mix:versionable");
-        node.setProperty("test", new Value[0], PropertyType.LONG);
-        node.save();
-        assertEquals(PropertyType.LONG, node.getProperty("test").getType());
-
-        Version version = node.checkin();
-        assertEquals(PropertyType.LONG, node.getProperty("test").getType());
-
-        node.restore(version, false);
-        assertEquals(
-                "JCR-1227: Restore of empty multivalue property always"
-                + " changes property type to String",
-                PropertyType.LONG, node.getProperty("test").getType());
-
-        node.checkout();
-        node.setProperty("test", new Value[0], PropertyType.BOOLEAN);
-        node.save();
-        assertEquals(PropertyType.BOOLEAN, node.getProperty("test").getType());
-
-        node.restore(version, false);
-        assertEquals(
-                "JCR-1227: Restore of empty multivalue property always"
-                + " changes property type to String",
-                PropertyType.LONG, node.getProperty("test").getType());
-    }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.integration;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.version.Version;
+
+import org.apache.jackrabbit.test.AbstractJCRTest;
+
+/**
+ * Integration tests for the Node implementation in Jackrabbit core.
+ */
+public class NodeImplTest extends AbstractJCRTest {
+
+    private Node node;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        node = testRootNode.addNode("testNodeImpl", "nt:unstructured");
+        testRootNode.save();
+    }
+
+    protected void tearDown() throws Exception {
+        node.remove();
+        testRootNode.save();
+        super.tearDown();
+    }
+
+    /**
+     * Test case for JCR-1389.
+     * 
+     * @see <a href="https://issues.apache.org/jira/browse/JCR-1389">JCR-1389</a>
+     */
+    public void testSetEmptyMultiValueProperty() throws RepositoryException {
+        Property property =
+            node.setProperty("test", new Value[0], PropertyType.LONG);
+        assertEquals(
+                "JCR-1389: setProperty(name, new Value[0], PropertyType.LONG)"
+                + " loses property type",
+                PropertyType.LONG, property.getType());
+    }
+
+    /**
+     * Test case for JCR-1227.
+     * 
+     * @see <a href="https://issues.apache.org/jira/browse/JCR-1227">JCR-1227</a>
+     */
+    public void testRestoreEmptyMultiValueProperty() throws Exception {
+        node.addMixin("mix:versionable");
+        node.setProperty("test", new Value[0], PropertyType.LONG);
+        node.save();
+        assertEquals(PropertyType.LONG, node.getProperty("test").getType());
+
+        Version version = node.checkin();
+        assertEquals(PropertyType.LONG, node.getProperty("test").getType());
+
+        node.restore(version, false);
+        assertEquals(
+                "JCR-1227: Restore of empty multivalue property always"
+                + " changes property type to String",
+                PropertyType.LONG, node.getProperty("test").getType());
+
+        node.checkout();
+        node.setProperty("test", new Value[0], PropertyType.BOOLEAN);
+        node.save();
+        assertEquals(PropertyType.BOOLEAN, node.getProperty("test").getType());
+
+        node.restore(version, false);
+        assertEquals(
+                "JCR-1227: Restore of empty multivalue property always"
+                + " changes property type to String",
+                PropertyType.LONG, node.getProperty("test").getType());
+    }
+
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/VersioningTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/VersioningTest.java
index 8d8a7f7..714927c 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/VersioningTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/VersioningTest.java
@@ -1,215 +1,215 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.integration;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.jcr.Node;
-import javax.jcr.Session;
-import javax.jcr.version.Version;
-
-import org.apache.jackrabbit.test.AbstractJCRTest;
-
-/**
- * Versioning tests.
- */
-public class VersioningTest extends AbstractJCRTest {
-
-    private Node n1;
-    private Node n2;
-
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        Session s1 = getHelper().getSuperuserSession();
-        n1 = s1.getRootNode().addNode("VersioningTest");
-        n1.addMixin(mixVersionable);
-        n1.getSession().save();
-
-        Session s2 = getHelper().getSuperuserSession(workspaceName);
-        s2.getWorkspace().clone(
-                s1.getWorkspace().getName(), n1.getPath(),
-                "/VersioningTest", true);
-        n2 = s2.getRootNode().getNode("VersioningTest");
-    }
-
-    protected void tearDown() throws Exception {
-        super.tearDown();
-
-        Session s1 = n1.getSession();
-        n1.remove();
-        s1.save();
-        s1.logout();
-
-        Session s2 = n2.getSession();
-        n2.remove();
-        s2.save();
-        s2.logout();
-    }
-
-    /**
-     * Tests that the version tree documented in
-     * AbstractVersionManager.calculateCheckinVersionName() can be
-     * constructed and has the expected version names.
-     * <p>
-     * Note that this test case needs to be modified if the version naming
-     * algorithm ever gets changed.
-     */
-    public void testVersionGraph() throws Exception {
-        Version vR = n1.getBaseVersion();
-
-        Version v10 = n1.checkin();
-        n1.checkout();
-        Version v11 = n1.checkin();
-        n1.checkout();
-        Version v12 = n1.checkin();
-        n1.checkout();
-        Version v13 = n1.checkin();
-        n1.checkout();
-        Version v14 = n1.checkin();
-        n1.checkout();
-        Version v15 = n1.checkin();
-        n1.checkout();
-        Version v16 = n1.checkin();
-
-        n1.restore(v12, true);
-        n1.checkout();
-        Version v120 = n1.checkin();
-        n1.checkout();
-        Version v121 = n1.checkin();
-        n1.checkout();
-        Version v122 = n1.checkin();
-
-        n1.restore(v12, true);
-        n1.checkout();
-        Version v1200 = n1.checkin();
-
-        n1.restore(v121, true);
-        n1.checkout();
-        Version v1210 = n1.checkin();
-        n1.checkout();
-        Version v1211 = n1.checkin();
-
-        // x.0 versions can be created if the newly created versionable
-        // node is cloned to another workspace before the first checkin
-        Version v20 = n2.checkin();
-
-        // Multiple branches can be merged using multiple workspaces
-        n2.restore(v122, true);
-        n1.restore(v16, true);
-        n1.checkout();
-        n1.merge(n2.getSession().getWorkspace().getName(), true);
-        n1.doneMerge(v122);
-        Version v17 = n1.checkin();
-
-        assertEquals("jcr:rootVersion", vR.getName());
-        assertPredecessors("", vR);
-        assertSuccessors("1.0 2.0", vR);
-        assertEquals("1.0", v10.getName());
-        assertPredecessors("jcr:rootVersion", v10);
-        assertSuccessors("1.1", v10);
-        assertEquals("1.1", v11.getName());
-        assertPredecessors("1.0", v11);
-        assertSuccessors("1.2", v11);
-        assertEquals("1.2", v12.getName());
-        assertPredecessors("1.1", v12);
-        assertSuccessors("1.3 1.2.0 1.2.0.0", v12);
-        assertEquals("1.3", v13.getName());
-        assertPredecessors("1.2", v13);
-        assertSuccessors("1.4", v13);
-        assertEquals("1.4", v14.getName());
-        assertPredecessors("1.3", v14);
-        assertSuccessors("1.5", v14);
-        assertEquals("1.5", v15.getName());
-        assertPredecessors("1.4", v15);
-        assertSuccessors("1.6", v15);
-        assertEquals("1.6", v16.getName());
-        assertPredecessors("1.5", v16);
-        assertSuccessors("1.7", v16);
-        assertEquals("1.7", v17.getName());
-        assertPredecessors("1.6 1.2.2", v17);
-        assertSuccessors("", v17);
-
-        assertEquals("1.2.0", v120.getName());
-        assertPredecessors("1.2", v120);
-        assertSuccessors("1.2.1", v120);
-        assertEquals("1.2.1", v121.getName());
-        assertPredecessors("1.2.0", v121);
-        assertSuccessors("1.2.2 1.2.1.0", v121);
-        assertEquals("1.2.2", v122.getName());
-        assertPredecessors("1.2.1", v122);
-        assertSuccessors("1.7", v122);
-
-        assertEquals("1.2.0.0", v1200.getName());
-        assertPredecessors("1.2", v1200);
-        assertSuccessors("", v1200);
-
-        assertEquals("1.2.1.0", v1210.getName());
-        assertPredecessors("1.2.1", v1210);
-        assertSuccessors("1.2.1.1", v1210);
-        assertEquals("1.2.1.1", v1211.getName());
-        assertPredecessors("1.2.1.0", v1211);
-        assertSuccessors("", v1211);
-
-        assertEquals("2.0", v20.getName());
-        assertPredecessors("jcr:rootVersion", v20);
-        assertSuccessors("", v20);
-    }
-
-    private void assertPredecessors(String expected, Version version)
-            throws Exception {
-        Set predecessors = new HashSet();
-        if (expected.length() > 0) {
-            predecessors.addAll(Arrays.asList(expected.split(" ")));
-        }
-        Version[] versions = version.getPredecessors();
-        for (int i = 0; i < versions.length; i++) {
-            if (!predecessors.remove(versions[i].getName())) {
-                fail("Version " + version.getName()
-                        + " has an unexpected predessor "
-                        + versions[i].getName());
-            }
-        }
-        if (!predecessors.isEmpty()) {
-            fail("Version " + version.getName()
-                    + " does not have all expected predecessors");
-        }
-    }
-
-    private void assertSuccessors(String expected, Version version)
-            throws Exception {
-        Set successors = new HashSet();
-        if (expected.length() > 0) {
-            successors.addAll(Arrays.asList(expected.split(" ")));
-        }
-        Version[] versions = version.getSuccessors();
-        for (int i = 0; i < versions.length; i++) {
-            if (!successors.remove(versions[i].getName())) {
-                fail("Version " + version.getName()
-                        + " has an unexpected successor "
-                        + versions[i].getName());
-            }
-        }
-        if (!successors.isEmpty()) {
-            fail("Version " + version.getName()
-                    + " does not have all expected successors");
-        }
-    }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.integration;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.Session;
+import javax.jcr.version.Version;
+
+import org.apache.jackrabbit.test.AbstractJCRTest;
+
+/**
+ * Versioning tests.
+ */
+public class VersioningTest extends AbstractJCRTest {
+
+    private Node n1;
+    private Node n2;
+
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        Session s1 = getHelper().getSuperuserSession();
+        n1 = s1.getRootNode().addNode("VersioningTest");
+        n1.addMixin(mixVersionable);
+        n1.getSession().save();
+
+        Session s2 = getHelper().getSuperuserSession(workspaceName);
+        s2.getWorkspace().clone(
+                s1.getWorkspace().getName(), n1.getPath(),
+                "/VersioningTest", true);
+        n2 = s2.getRootNode().getNode("VersioningTest");
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+
+        Session s1 = n1.getSession();
+        n1.remove();
+        s1.save();
+        s1.logout();
+
+        Session s2 = n2.getSession();
+        n2.remove();
+        s2.save();
+        s2.logout();
+    }
+
+    /**
+     * Tests that the version tree documented in
+     * AbstractVersionManager.calculateCheckinVersionName() can be
+     * constructed and has the expected version names.
+     * <p>
+     * Note that this test case needs to be modified if the version naming
+     * algorithm ever gets changed.
+     */
+    public void testVersionGraph() throws Exception {
+        Version vR = n1.getBaseVersion();
+
+        Version v10 = n1.checkin();
+        n1.checkout();
+        Version v11 = n1.checkin();
+        n1.checkout();
+        Version v12 = n1.checkin();
+        n1.checkout();
+        Version v13 = n1.checkin();
+        n1.checkout();
+        Version v14 = n1.checkin();
+        n1.checkout();
+        Version v15 = n1.checkin();
+        n1.checkout();
+        Version v16 = n1.checkin();
+
+        n1.restore(v12, true);
+        n1.checkout();
+        Version v120 = n1.checkin();
+        n1.checkout();
+        Version v121 = n1.checkin();
+        n1.checkout();
+        Version v122 = n1.checkin();
+
+        n1.restore(v12, true);
+        n1.checkout();
+        Version v1200 = n1.checkin();
+
+        n1.restore(v121, true);
+        n1.checkout();
+        Version v1210 = n1.checkin();
+        n1.checkout();
+        Version v1211 = n1.checkin();
+
+        // x.0 versions can be created if the newly created versionable
+        // node is cloned to another workspace before the first checkin
+        Version v20 = n2.checkin();
+
+        // Multiple branches can be merged using multiple workspaces
+        n2.restore(v122, true);
+        n1.restore(v16, true);
+        n1.checkout();
+        n1.merge(n2.getSession().getWorkspace().getName(), true);
+        n1.doneMerge(v122);
+        Version v17 = n1.checkin();
+
+        assertEquals("jcr:rootVersion", vR.getName());
+        assertPredecessors("", vR);
+        assertSuccessors("1.0 2.0", vR);
+        assertEquals("1.0", v10.getName());
+        assertPredecessors("jcr:rootVersion", v10);
+        assertSuccessors("1.1", v10);
+        assertEquals("1.1", v11.getName());
+        assertPredecessors("1.0", v11);
+        assertSuccessors("1.2", v11);
+        assertEquals("1.2", v12.getName());
+        assertPredecessors("1.1", v12);
+        assertSuccessors("1.3 1.2.0 1.2.0.0", v12);
+        assertEquals("1.3", v13.getName());
+        assertPredecessors("1.2", v13);
+        assertSuccessors("1.4", v13);
+        assertEquals("1.4", v14.getName());
+        assertPredecessors("1.3", v14);
+        assertSuccessors("1.5", v14);
+        assertEquals("1.5", v15.getName());
+        assertPredecessors("1.4", v15);
+        assertSuccessors("1.6", v15);
+        assertEquals("1.6", v16.getName());
+        assertPredecessors("1.5", v16);
+        assertSuccessors("1.7", v16);
+        assertEquals("1.7", v17.getName());
+        assertPredecessors("1.6 1.2.2", v17);
+        assertSuccessors("", v17);
+
+        assertEquals("1.2.0", v120.getName());
+        assertPredecessors("1.2", v120);
+        assertSuccessors("1.2.1", v120);
+        assertEquals("1.2.1", v121.getName());
+        assertPredecessors("1.2.0", v121);
+        assertSuccessors("1.2.2 1.2.1.0", v121);
+        assertEquals("1.2.2", v122.getName());
+        assertPredecessors("1.2.1", v122);
+        assertSuccessors("1.7", v122);
+
+        assertEquals("1.2.0.0", v1200.getName());
+        assertPredecessors("1.2", v1200);
+        assertSuccessors("", v1200);
+
+        assertEquals("1.2.1.0", v1210.getName());
+        assertPredecessors("1.2.1", v1210);
+        assertSuccessors("1.2.1.1", v1210);
+        assertEquals("1.2.1.1", v1211.getName());
+        assertPredecessors("1.2.1.0", v1211);
+        assertSuccessors("", v1211);
+
+        assertEquals("2.0", v20.getName());
+        assertPredecessors("jcr:rootVersion", v20);
+        assertSuccessors("", v20);
+    }
+
+    private void assertPredecessors(String expected, Version version)
+            throws Exception {
+        Set predecessors = new HashSet();
+        if (expected.length() > 0) {
+            predecessors.addAll(Arrays.asList(expected.split(" ")));
+        }
+        Version[] versions = version.getPredecessors();
+        for (int i = 0; i < versions.length; i++) {
+            if (!predecessors.remove(versions[i].getName())) {
+                fail("Version " + version.getName()
+                        + " has an unexpected predessor "
+                        + versions[i].getName());
+            }
+        }
+        if (!predecessors.isEmpty()) {
+            fail("Version " + version.getName()
+                    + " does not have all expected predecessors");
+        }
+    }
+
+    private void assertSuccessors(String expected, Version version)
+            throws Exception {
+        Set successors = new HashSet();
+        if (expected.length() > 0) {
+            successors.addAll(Arrays.asList(expected.split(" ")));
+        }
+        Version[] versions = version.getSuccessors();
+        for (int i = 0; i < versions.length; i++) {
+            if (!successors.remove(versions[i].getName())) {
+                fail("Version " + version.getName()
+                        + " has an unexpected successor "
+                        + versions[i].getName());
+            }
+        }
+        if (!successors.isEmpty()) {
+            fail("Version " + version.getName()
+                    + " does not have all expected successors");
+        }
+    }
+
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/daily/DailyIntegrationTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/daily/DailyIntegrationTest.java
index 96ee071..d07a7d5 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/daily/DailyIntegrationTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/integration/daily/DailyIntegrationTest.java
@@ -20,6 +20,7 @@ import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
 import org.apache.jackrabbit.core.ConcurrencyTest;
+import org.apache.jackrabbit.core.ConcurrentAddMoveRemoveTest;
 import org.apache.jackrabbit.core.ConcurrentCheckinMixedTransactionTest;
 import org.apache.jackrabbit.core.ConcurrentLoginTest;
 import org.apache.jackrabbit.core.ConcurrentNodeModificationTest;
@@ -30,7 +31,7 @@ import org.apache.jackrabbit.core.ConcurrentVersioningWithTransactionsTest;
 import org.apache.jackrabbit.core.LockTest;
 import org.apache.jackrabbit.core.ReadVersionsWhileModified;
 import org.apache.jackrabbit.core.integration.ConcurrentQueriesWithUpdatesTest;
-import org.apache.jackrabbit.core.query.LargeResultSetTest;
+import org.apache.jackrabbit.core.query.lucene.LargeResultSetTest;
 import org.apache.jackrabbit.core.lock.ConcurrentLockingTest;
 import org.apache.jackrabbit.core.lock.ConcurrentLockingWithTransactionsTest;
 
@@ -54,6 +55,7 @@ public class DailyIntegrationTest extends TestCase {
         suite.addTestSuite(ConcurrentVersioningTest.class);
         suite.addTestSuite(ConcurrentVersioningWithTransactionsTest.class);
         suite.addTestSuite(ConcurrentCheckinMixedTransactionTest.class);
+        suite.addTestSuite(ConcurrentAddMoveRemoveTest.class);
         suite.addTestSuite(LockTest.class);
         suite.addTestSuite(ReadVersionsWhileModified.class);
         suite.addTestSuite(ConcurrentLockingTest.class);
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/nodetype/xml/TestAll.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/nodetype/xml/TestAll.java
index 59238df..39ea01c 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/nodetype/xml/TestAll.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/nodetype/xml/TestAll.java
@@ -248,7 +248,7 @@ public class TestAll extends AbstractJCRTest {
     /** Test for namespace registration on node type import. */
     public void testImportXMLNodeTypes() throws Exception {
         try {
-            superuser.getNamespacePrefix("test-namespace2");
+            superuser.getNamespacePrefix("http://ns.example.org/test-namespace2");
             // Ignore test case, node type and namespace already registered
         } catch (NamespaceException e1) {
             // Namespace testns2 not yet registered
@@ -258,7 +258,7 @@ public class TestAll extends AbstractJCRTest {
                     TestAll.class.getResourceAsStream(TEST_NS_XML_NODETYPES),
                     JackrabbitNodeTypeManager.TEXT_XML);
             try {
-                superuser.getNamespacePrefix("test-namespace2");
+                superuser.getNamespacePrefix("http://ns.example.org/test-namespace2");
             } catch (NamespaceException e2) {
                 fail("xml test2 namespace not registered");
             }
@@ -286,14 +286,14 @@ public class TestAll extends AbstractJCRTest {
     /** Test for namespace registration on node type import. */
     public void testImportCNDNodeTypes() throws Exception {
         try {
-            superuser.getNamespacePrefix("test-namespace3");
+            superuser.getNamespacePrefix("http://ns.example.org/test-namespace3");
             // Ignore test case, node type and namespace already registered
         } catch (NamespaceException e1) {
             Reader cnd = new InputStreamReader(TestAll.class.getResourceAsStream(TEST_NS_CND_NODETYPES));
             CndImporter.registerNodeTypes(cnd, superuser);
             cnd.close();
             try {
-                superuser.getNamespacePrefix("test-namespace3");
+                superuser.getNamespacePrefix("http://ns.example.org/test-namespace3");
             } catch (NamespaceException e2) {
                 fail("cnd test3 namespace not registered");
             }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/MixinTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/MixinTest.java
index 6211b68..4cb8514 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/MixinTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/MixinTest.java
@@ -40,16 +40,18 @@ public class MixinTest extends AbstractObservationTest {
 
         EventResult propertyAddedListener = new EventResult(log);
         addEventListener(propertyAddedListener, new String[]{mixReferenceable}, Event.PROPERTY_ADDED);
-
-        testRootNode.getNode(nodeName1).setProperty(propertyName1, "test");
-        testRootNode.getNode(nodeName2).setProperty(propertyName1, "test");
-        testRootNode.getNode(nodeName3).setProperty(propertyName1, "test");
-        testRootNode.save();
-
-        removeEventListener(propertyAddedListener);
-        Event[] added = propertyAddedListener.getEvents(DEFAULT_WAIT_TIMEOUT);
-        checkPropertyAdded(added, new String[]{nodeName1 + "/" + propertyName1,
-                                               nodeName3 + "/" + propertyName1});
+        try {
+            testRootNode.getNode(nodeName1).setProperty(propertyName1, "test");
+            testRootNode.getNode(nodeName2).setProperty(propertyName1, "test");
+            testRootNode.getNode(nodeName3).setProperty(propertyName1, "test");
+            testRootNode.save();
+
+            Event[] added = propertyAddedListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+            checkPropertyAdded(added, new String[]{nodeName1 + "/" + propertyName1,
+                                                   nodeName3 + "/" + propertyName1});
+        } finally {
+            removeEventListener(propertyAddedListener);
+        }
     }
 
     /**
@@ -63,17 +65,19 @@ public class MixinTest extends AbstractObservationTest {
 
         EventResult propertyAddedListener = new EventResult(log);
         addEventListener(propertyAddedListener, new String[]{mixReferenceable, mixLockable}, Event.PROPERTY_ADDED);
-
-        testRootNode.getNode(nodeName1).setProperty(propertyName1, "test");
-        testRootNode.getNode(nodeName2).setProperty(propertyName1, "test");
-        testRootNode.getNode(nodeName3).setProperty(propertyName1, "test");
-        testRootNode.save();
-
-        removeEventListener(propertyAddedListener);
-        Event[] added = propertyAddedListener.getEvents(DEFAULT_WAIT_TIMEOUT);
-        checkPropertyAdded(added, new String[]{nodeName1 + "/" + propertyName1,
-                                               nodeName2 + "/" + propertyName1,
-                                               nodeName3 + "/" + propertyName1});
+        try {
+            testRootNode.getNode(nodeName1).setProperty(propertyName1, "test");
+            testRootNode.getNode(nodeName2).setProperty(propertyName1, "test");
+            testRootNode.getNode(nodeName3).setProperty(propertyName1, "test");
+            testRootNode.save();
+
+            Event[] added = propertyAddedListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+            checkPropertyAdded(added, new String[]{nodeName1 + "/" + propertyName1,
+                                                   nodeName2 + "/" + propertyName1,
+                                                   nodeName3 + "/" + propertyName1});
+        } finally {
+            removeEventListener(propertyAddedListener);
+        }
     }
 
     /**
@@ -91,16 +95,18 @@ public class MixinTest extends AbstractObservationTest {
 
         EventResult propertyAddedListener = new EventResult(log);
         addEventListener(propertyAddedListener, new String[]{mixReferenceable}, Event.PROPERTY_ADDED);
-
-        node1.setProperty(propertyName1, "test");
-        node2.setProperty(propertyName1, "test");
-        node3.setProperty(propertyName1, "test");
-        testRootNode.save();
-
-        removeEventListener(propertyAddedListener);
-        Event[] added = propertyAddedListener.getEvents(DEFAULT_WAIT_TIMEOUT);
-        checkPropertyAdded(added, new String[]{nodeName1 + "/" + propertyName1,
-                                               nodeName3 + "/" + propertyName1});
+        try {
+            node1.setProperty(propertyName1, "test");
+            node2.setProperty(propertyName1, "test");
+            node3.setProperty(propertyName1, "test");
+            testRootNode.save();
+
+            Event[] added = propertyAddedListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+            checkPropertyAdded(added, new String[]{nodeName1 + "/" + propertyName1,
+                                                   nodeName3 + "/" + propertyName1});
+        } finally {
+            removeEventListener(propertyAddedListener);
+        }
     }
 
     /**
@@ -117,13 +123,15 @@ public class MixinTest extends AbstractObservationTest {
         EventResult propertyAddedListener = new EventResult(log);
         // mix:versionable is derived from mix:referenceable
         addEventListener(propertyAddedListener, new String[]{mixReferenceable}, Event.PROPERTY_ADDED);
-
-        node1.setProperty(propertyName1, "test");
-        testRootNode.save();
-
-        removeEventListener(propertyAddedListener);
-        Event[] added = propertyAddedListener.getEvents(DEFAULT_WAIT_TIMEOUT);
-        checkPropertyAdded(added, new String[]{nodeName1 + "/" + propertyName1});
+        try {
+            node1.setProperty(propertyName1, "test");
+            testRootNode.save();
+
+            Event[] added = propertyAddedListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+            checkPropertyAdded(added, new String[]{nodeName1 + "/" + propertyName1});
+        } finally {
+            removeEventListener(propertyAddedListener);
+        }
     }
 
     /**
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/ReorderTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/ReorderTest.java
index 0bf7213..f1b0b5b 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/ReorderTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/ReorderTest.java
@@ -33,7 +33,7 @@ public class ReorderTest extends AbstractObservationTest {
      * Tests if reordering a child node triggers a {@link javax.jcr.observation.Event#NODE_REMOVED}
      * and a {@link javax.jcr.observation.Event#NODE_ADDED} event with same name siblings. Furthermore
      * a node is removed in the same save scope.
-     * <p/>
+     * <p>
      * Because of the one reorder operation, three nodes change their index. And
      * actually four nodes change their context position. The minimal events
      * that may be triggered only includes one pair of remove/add node events
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/TestAll.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/TestAll.java
index 417e3fb..ba3d101 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/TestAll.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/TestAll.java
@@ -41,6 +41,7 @@ public class TestAll extends TestCase {
         suite.addTestSuite(VersionEventsTest.class);
         suite.addTestSuite(MoveInPlaceTest.class);
         suite.addTestSuite(ShareableNodesTest.class);
+        suite.addTestSuite(WarningOnSaveWithNotificationThreadTest.class);
 
         return suite;
     }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/WarningOnSaveWithNotificationThreadTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/WarningOnSaveWithNotificationThreadTest.java
new file mode 100644
index 0000000..3e76661
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/observation/WarningOnSaveWithNotificationThreadTest.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.observation;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.EventListener;
+
+import org.apache.jackrabbit.core.Tail;
+import org.apache.jackrabbit.test.api.observation.AbstractObservationTest;
+import org.apache.jackrabbit.test.api.observation.EventResult;
+
+/**
+ * <code>WarningOnSaveWithNotificationThreadTest</code> checks if the repository
+ * writes a warning to the log file when changes are performed with the event
+ * notification thread. See JCR-3426.
+ */
+public class WarningOnSaveWithNotificationThreadTest extends
+        AbstractObservationTest {
+
+    private static final String MESSAGE = "Save call with event notification thread detected";
+
+    public void testWarning() throws Exception {
+        final List<Exception> exceptions = new ArrayList<Exception>();
+        EventResult result = new EventResult(log) {
+            @Override
+            public void onEvent(EventIterator events) {
+                try {
+                    Session s = getHelper().getSuperuserSession();
+                    try {
+                        s.getNode(testRoot).addNode("bar");
+                        s.save();
+                    } finally {
+                        s.logout();
+                    }
+                } catch (RepositoryException e) {
+                    exceptions.add(e);
+                }
+                super.onEvent(events);
+            }
+        };
+        addEventListener(result);
+
+        Tail tail = Tail.start(new File("target", "jcr.log"), MESSAGE);
+        try {
+            testRootNode.addNode("foo");
+            superuser.save();
+
+            removeEventListener(result);
+            result.getEvents(5000);
+            assertTrue("Warn message expected in log file.",
+                    tail.getLines().iterator().hasNext());
+        } finally {
+            tail.close();
+        }
+
+        if (!exceptions.isEmpty()) {
+            fail(exceptions.get(0).toString());
+        }
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/AutoFixCorruptNode.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/AutoFixCorruptNode.java
index 9fdefae..b21932d 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/AutoFixCorruptNode.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/AutoFixCorruptNode.java
@@ -76,7 +76,7 @@ public class AutoFixCorruptNode extends TestCase {
 
         s = openSession(rep, false);
         try {
-            ConsistencyReport r = TestHelper.checkConsistency(s, false);
+            ConsistencyReport r = TestHelper.checkConsistency(s, false, null);
             assertNotNull(r);
             assertNotNull(r.getItems());
             assertEquals(1, r.getItems().size());
@@ -89,6 +89,53 @@ public class AutoFixCorruptNode extends TestCase {
         }
     }
 
+    public void testOrphan() throws Exception {
+
+        // new repository
+        TransientRepository rep = new TransientRepository(new File(TEST_DIR));
+        Session s = openSession(rep, false);
+
+        try {
+            Node root = s.getRootNode();
+
+            Node parent = root.addNode("parent");
+            Node test = parent.addNode("test");
+            test.addMixin("mix:referenceable");
+
+            String lost = test.getIdentifier();
+
+            Node lnf = root.addNode("lost+found");
+            lnf.addMixin("mix:referenceable");
+            String lnfid = lnf.getIdentifier();
+
+            s.save();
+
+            Node brokenNode = parent;
+            UUID destroy = UUID.fromString(brokenNode.getIdentifier());
+            s.logout();
+
+            destroyBundle(destroy, "workspaces/default");
+
+            s = openSession(rep, false);
+            ConsistencyReport report = TestHelper.checkConsistency(s, false, null);
+            assertTrue("Report should have reported broken nodes", !report.getItems().isEmpty());
+
+            // now retry with lost+found functionality
+            ConsistencyReport report2 = TestHelper.checkConsistency(s, true, lnfid);
+            assertTrue("Report should have reported broken nodes", !report2.getItems().isEmpty());
+
+            s.logout();
+
+            s = openSession(rep, false);
+            Node q = s.getNodeByIdentifier(lost);
+
+            // check the node was moved
+            assertEquals(lnfid, q.getParent().getIdentifier());
+        } finally {
+            s.logout();
+        }
+    }
+
     public void testMissingVHR() throws Exception {
 
         // new repository
@@ -115,14 +162,14 @@ public class AutoFixCorruptNode extends TestCase {
             String vhrRootVersionId = vhr.getNode("jcr:rootVersion").getIdentifier();
             UUID destroy = UUID.fromString(brokenNode.getIdentifier());
             s.logout();
-            
+
             destroyBundle(destroy, "version");
 
             s = openSession(rep, false);
 
-            ConsistencyReport report = TestHelper.checkVersionStoreConsistency(s, false);
+            ConsistencyReport report = TestHelper.checkVersionStoreConsistency(s, false, null);
             assertTrue("Report should have reported broken nodes", !report.getItems().isEmpty());
-            
+
             try {
                 test = s.getRootNode().getNode("test");
                 vhr = s.getWorkspace().getVersionManager()
@@ -141,31 +188,31 @@ public class AutoFixCorruptNode extends TestCase {
             test = s.getRootNode().getNode("test");
             // versioning should be disabled now
             assertFalse(test.isNodeType("mix:versionable"));
-            
+
             try {
                 // try to enable versioning again
                 test.addMixin("mix:versionable");
                 s.save();
-                
+
                 fail("enabling versioning succeeded unexpectedly");
             }
             catch (Exception e) {
                 // we expect this to fail
             }
-            
+
             s.logout();
-            
+
             // now redo after running fixup on versioning storage
             s = openSession(rep, false);
 
-            report = TestHelper.checkVersionStoreConsistency(s, true);
+            report = TestHelper.checkVersionStoreConsistency(s, true, null);
             assertTrue("Report should have reported broken nodes", !report.getItems().isEmpty());
             int reportitems = report.getItems().size();
-            
+
             // problems should now be fixed
-            report = TestHelper.checkVersionStoreConsistency(s, false);
+            report = TestHelper.checkVersionStoreConsistency(s, false, null);
             assertTrue("Some problems should have been fixed but are not: " + report, report.getItems().size() < reportitems);
-            
+
             // get a fresh session
             s.logout();
             s = openSession(rep, false);
@@ -173,11 +220,11 @@ public class AutoFixCorruptNode extends TestCase {
             test = s.getRootNode().getNode("test");
             // versioning should be disabled now
             assertFalse(test.isNodeType("mix:versionable"));
-            
+
             // try to enable versioning again
             test.addMixin("mix:versionable");
             s.save();
-            
+
             Node oldRootVersion = s.getNodeByIdentifier(vhrRootVersionId);
             try {
                 String path = oldRootVersion.getPath();
@@ -186,7 +233,7 @@ public class AutoFixCorruptNode extends TestCase {
             catch (ItemNotFoundException ex) {
                 // orphaned
             }
-            
+
             Node newRootVersion = s.getWorkspace().getVersionManager()
                     .getVersionHistory(test.getPath()).getRootVersion();
             assertFalse(
@@ -215,7 +262,8 @@ public class AutoFixCorruptNode extends TestCase {
             Node root = s.getRootNode();
 
             // add nodes /test and /test/missing
-            Node test = root.addNode("test");
+            Node test = root.addNode("test", "nt:file");
+            test.addNode("jcr:content", "nt:unstructured");
             test.addMixin("mix:versionable");
 
             s.save();
@@ -227,17 +275,17 @@ public class AutoFixCorruptNode extends TestCase {
 
             Node brokenNode = vhr.getNode("jcr:rootVersion");
             String vhrId = vhr.getIdentifier();
-            
+
             UUID destroy = UUID.fromString(brokenNode.getIdentifier());
             s.logout();
-            
+
             destroyBundle(destroy, "version");
 
             s = openSession(rep, false);
 
-            ConsistencyReport report = TestHelper.checkVersionStoreConsistency(s, false);
+            ConsistencyReport report = TestHelper.checkVersionStoreConsistency(s, false, null);
             assertTrue("Report should have reported broken nodes", !report.getItems().isEmpty());
-            
+
             try {
                 test = s.getRootNode().getNode("test");
                 vhr = s.getWorkspace().getVersionManager()
@@ -256,39 +304,42 @@ public class AutoFixCorruptNode extends TestCase {
             test = s.getRootNode().getNode("test");
             // versioning should be disabled now
             assertFalse(test.isNodeType("mix:versionable"));
-            
+
             try {
                 // try to enable versioning again
                 test.addMixin("mix:versionable");
                 s.save();
-                
+
                 fail("enabling versioning succeeded unexpectedly");
             }
             catch (Exception e) {
                 // we expect this to fail
             }
-            
+
             s.logout();
-            
+
             // now redo after running fixup on versioning storage
             s = openSession(rep, false);
 
-            report = TestHelper.checkVersionStoreConsistency(s, true);
+            report = TestHelper.checkVersionStoreConsistency(s, true, null);
             assertTrue("Report should have reported broken nodes", !report.getItems().isEmpty());
             int reportitems = report.getItems().size();
-            
+
             // problems should now be fixed
-            report = TestHelper.checkVersionStoreConsistency(s, false);
+            report = TestHelper.checkVersionStoreConsistency(s, false, null);
             assertTrue("Some problems should have been fixed but are not: " + report, report.getItems().size() < reportitems);
-            
+
             test = s.getRootNode().getNode("test");
             // versioning should be disabled now
             assertFalse(test.isNodeType("mix:versionable"));
-            
+            // jcr:uuid property should still be present
+            assertTrue(test.hasProperty("jcr:uuid"));
+            // ...and have a proper definition
+            assertNotNull(test.getProperty("jcr:uuid").getDefinition().getName());
             // try to enable versioning again
             test.addMixin("mix:versionable");
             s.save();
-            
+
             Node oldVHR = s.getNodeByIdentifier(vhrId);
             Node newVHR = s.getWorkspace().getVersionManager().getVersionHistory(test.getPath());
 
@@ -298,11 +349,11 @@ public class AutoFixCorruptNode extends TestCase {
 
             // name should be same plus suffix
             assertTrue(oldVHR.getName().startsWith(newVHR.getName()));
-            
+
             // try a checkout / checkin
             s.getWorkspace().getVersionManager().checkout(test.getPath());
             s.getWorkspace().getVersionManager().checkin(test.getPath());
-            
+
         } finally {
             s.logout();
             System.setProperty("org.apache.jackrabbit.version.recovery",
@@ -337,23 +388,23 @@ public class AutoFixCorruptNode extends TestCase {
 
             Node brokenNode = vhr.getNode("jcr:rootVersion");
             String vhrId = vhr.getIdentifier();
-            
+
             UUID destroy = UUID.fromString(brokenNode.getIdentifier());
 
             // disable versioning
             test.removeMixin("mix:versionable");
             s.save();
-            
+
             s.logout();
-            
-            
+
+
             destroyBundle(destroy, "version");
 
             s = openSession(rep, false);
 
-            ConsistencyReport report = TestHelper.checkVersionStoreConsistency(s, false);
+            ConsistencyReport report = TestHelper.checkVersionStoreConsistency(s, false, null);
             assertTrue("Report should have reported broken nodes", !report.getItems().isEmpty());
-            
+
             s.logout();
 
             System.setProperty("org.apache.jackrabbit.version.recovery", "true");
@@ -366,7 +417,7 @@ public class AutoFixCorruptNode extends TestCase {
             test = s.getRootNode().getNode("test");
             // versioning should still be disabled
             assertFalse(test.isNodeType("mix:versionable"));
-            
+
             // try to enable versioning again
             test.addMixin("mix:versionable");
             s.save();
@@ -380,7 +431,7 @@ public class AutoFixCorruptNode extends TestCase {
 
             // name should be same plus suffix
             assertTrue(oldVHR.getName().startsWith(newVHR.getName()));
-            
+
             // try a checkout / checkin
             s.getWorkspace().getVersionManager().checkout(test.getPath());
             s.getWorkspace().getVersionManager().checkin(test.getPath());
@@ -426,7 +477,7 @@ public class AutoFixCorruptNode extends TestCase {
 
             s = openSession(rep, false);
 
-            ConsistencyReport report = TestHelper.checkVersionStoreConsistency(s, true);
+            ConsistencyReport report = TestHelper.checkVersionStoreConsistency(s, true, null);
             assertTrue("Report should have reported broken nodes", !report.getItems().isEmpty());
 
             s.logout();
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/PersistenceManagerTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/PersistenceManagerTest.java
index 0cedb02..d314f48 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/PersistenceManagerTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/PersistenceManagerTest.java
@@ -18,8 +18,6 @@ package org.apache.jackrabbit.core.persistence;
 
 import java.io.File;
 import java.util.Arrays;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
 
 import javax.jcr.PropertyType;
 
@@ -31,8 +29,6 @@ import org.apache.jackrabbit.core.RepositoryImpl;
 import org.apache.jackrabbit.core.fs.mem.MemoryFileSystem;
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.id.PropertyId;
-import org.apache.jackrabbit.core.persistence.PMContext;
-import org.apache.jackrabbit.core.persistence.PersistenceManager;
 import org.apache.jackrabbit.core.persistence.mem.InMemBundlePersistenceManager;
 import org.apache.jackrabbit.core.persistence.mem.InMemPersistenceManager;
 import org.apache.jackrabbit.core.persistence.obj.ObjectPersistenceManager;
@@ -43,7 +39,7 @@ import org.apache.jackrabbit.core.state.NoSuchItemStateException;
 import org.apache.jackrabbit.core.state.NodeReferences;
 import org.apache.jackrabbit.core.state.NodeState;
 import org.apache.jackrabbit.core.state.PropertyState;
-import org.apache.jackrabbit.core.stats.RepositoryStatisticsImpl;
+import org.apache.jackrabbit.stats.RepositoryStatisticsImpl;
 import org.apache.jackrabbit.core.util.db.ConnectionFactory;
 import org.apache.jackrabbit.core.value.InternalValue;
 import org.apache.jackrabbit.spi.Name;
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/util/BundleBindingRandomizedTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/util/BundleBindingRandomizedTest.java
new file mode 100644
index 0000000..a7c174b
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/util/BundleBindingRandomizedTest.java
@@ -0,0 +1,231 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.persistence.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Random;
+import java.util.Set;
+
+import javax.jcr.PropertyType;
+
+import junit.framework.TestCase;
+
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.id.PropertyId;
+import org.apache.jackrabbit.core.persistence.util.NodePropBundle.PropertyEntry;
+import org.apache.jackrabbit.core.util.StringIndex;
+import org.apache.jackrabbit.core.value.InternalValue;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.NameFactory;
+import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
+
+/**
+ * A randomized test for the BundleBinding writer and reader classes.
+ */
+public class BundleBindingRandomizedTest extends TestCase {
+
+    private static final NameFactory factory = NameFactoryImpl.getInstance();
+
+    /**
+     * Tests serialization of a complex bundle.
+     */
+    public void testRandomBundle() throws Exception {
+        int seed = 0;
+        for (int i=0; i<100;) {
+            Random r = new Random(seed++);
+            NodePropBundle bundle;
+            try {
+                bundle = randomBundle(r);
+            } catch (IllegalArgumentException e) {
+                continue;
+            }
+            try {
+                if (tryBundleRoundtrip(bundle)) {
+                    i++;
+                }
+            } catch (Exception e) {
+                throw new Exception(
+                        "Error round-tripping bundle with seed " + seed, e);
+                
+            }
+        }
+    }
+    
+    private static NodePropBundle randomBundle(Random r) {
+        NodeId id = randomNodeId(r);
+        NodePropBundle bundle = new NodePropBundle(id);
+        bundle.setModCount((short) randomSize(r)); 
+        if (r.nextInt(10) > 0) {
+            bundle.setParentId(randomNodeId(r));
+        }
+        if (r.nextInt(100) > 0) {
+            bundle.setNodeTypeName(randomName(r));
+        }
+        if (r.nextInt(100) > 0) {
+            bundle.setMixinTypeNames(randomNameSet(r));
+        }
+        if (r.nextInt(10) > 0) {
+            bundle.setReferenceable(r.nextBoolean());
+        }
+        if (r.nextInt(10) > 0) {
+            bundle.setSharedSet(randomNodeIdSet(r));
+        }
+        int count = randomSize(r);
+        for (int i=0; i<count; i++) {
+            PropertyEntry p = randomProperty(id, r);
+            bundle.addProperty(p);
+        }
+        count = randomSize(r);
+        for (int i=0; i<count; i++) {
+            bundle.addChildNodeEntry(randomName(r), randomNodeId(r));
+        }
+        return bundle;
+    }
+    
+    private static boolean tryBundleRoundtrip(NodePropBundle bundle)
+            throws Exception {
+        BundleBinding binding;
+        StringIndex index = new StringIndex() {
+            @Override
+            public int stringToIndex(String string) {
+                return 0;
+            }
+            @Override
+            public String indexToString(int idx) {
+                return "";
+            }
+        };
+        binding = new BundleBinding(null, null, index, index, null);
+        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+        try {
+            binding.writeBundle(buffer, bundle);
+        } catch (Exception e) {
+            // ignore - error found
+            return false;
+        }
+        byte[] bytes = buffer.toByteArray();
+        NodePropBundle b2 = binding.readBundle(
+                new ByteArrayInputStream(bytes), bundle.getId());
+        if (!bundle.equals(b2)) {
+            // if debugging is needed:
+            // buffer = new ByteArrayOutputStream();
+            // binding.writeBundle(buffer, bundle);
+            // bytes = buffer.toByteArray();
+            // b2 = binding.readBundle(
+            // new ByteArrayInputStream(bytes), bundle.getId());
+            assertEquals(bundle.toString(), b2.toString());
+        }
+        return true;
+    }
+    
+    private static PropertyEntry randomProperty(NodeId nodeId, Random r) {
+        PropertyEntry p = new PropertyEntry(
+                new PropertyId(nodeId, randomName(r)));        
+        int type = PropertyType.STRING;
+        if (r.nextInt(10) == 0) {
+            type = r.nextInt() + 15;
+        }
+        p.setType(type);
+        p.setModCount((short) randomSize(r)); 
+        boolean multiValued = r.nextBoolean();
+        p.setMultiValued(multiValued);
+        int size;
+        if (multiValued && r.nextInt(10) > 0) {
+            size = 1;
+        } else {
+            size = randomSize(r);
+        }
+        InternalValue[] values = new InternalValue[size];
+        for (int i = 0; i < size; i++) {
+            values[i] = randomValue(r);
+        }
+        p.setValues(values);
+        return p;
+    }
+    
+    private static InternalValue randomValue(Random r) {
+        if (r.nextInt(50) == 0) {
+            return null;
+        }
+        // TODO currently only string values
+        return InternalValue.create(randomString(r));
+    }
+
+    private static int randomSize(Random r) {
+        if (r.nextInt(20) == 0) {
+            return 0;
+        } else if (r.nextInt(20) == 0) {
+            return 1;
+        } else if (r.nextInt(20) == 0) {
+            return r.nextInt(10000);
+        }
+        return r.nextInt(5) + 1;
+    }
+    
+    private static Set<Name> randomNameSet(Random r) {
+        if (r.nextInt(100) == 0) {
+            return null;
+        }
+        int size = randomSize(r);
+        HashSet<Name> set = new HashSet<Name>();
+        for(int i=0; i<size; i++) {
+            set.add(randomName(r));
+        }
+        return set;
+    }
+    
+    private static Set<NodeId> randomNodeIdSet(Random r) {
+        if (r.nextInt(100) == 0) {
+            return null;
+        } else if (r.nextInt(10) == 0) {
+            return Collections.emptySet();
+        }
+        int size = randomSize(r);
+        HashSet<NodeId> set = new HashSet<NodeId>();
+        for(int i=0; i<size; i++) {
+            set.add(randomNodeId(r));
+        }
+        return set;
+    }
+    
+    private static NodeId randomNodeId(Random r) {
+        if (r.nextInt(100) == 0) {
+            return null;
+        }
+        return new NodeId(r.nextLong(), r.nextLong());
+    }
+    
+    private static Name randomName(Random r) {
+        if (r.nextInt(100) == 0) {
+            return null;
+        }
+        return factory.create(randomString(r), randomString(r));
+    }
+    
+    private static String randomString(Random r) {
+        int size = randomSize(r);
+        StringBuilder buff = new StringBuilder();
+        for (int i=0; i<size; i++) {
+            buff.append("abcd".charAt(r.nextInt(4)));
+        }
+        return buff.toString();
+    }
+    
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/util/NodeCorruptionTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/util/NodeCorruptionTest.java
new file mode 100644
index 0000000..43703c6
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/util/NodeCorruptionTest.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.persistence.util;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.jackrabbit.test.NotExecutableException;
+
+/**
+ * Tests that a node or a node bundle can not get internally corrupt.
+ */
+public class NodeCorruptionTest extends AbstractJCRTest {
+
+    public void testCopyMultiSingleValue() throws Exception {
+        Node root = superuser.getRootNode();
+        String nodeName = "testCopyMulti" + System.currentTimeMillis();
+        if (root.hasNode(nodeName)) {
+            root.getNode(nodeName).remove();
+            superuser.save();
+        }
+        Node test = root.addNode(nodeName);
+        test.setProperty("x", "Hi");
+        superuser.save();
+
+        String wsp = superuser.getWorkspace().getName();
+        String workspace2 = getAlternativeWorkspaceName();
+        if (workspace2 == null) {
+            throw new NotExecutableException();
+        }
+        Session s2 = getHelper().getSuperuserSession(workspace2);
+        s2.getWorkspace().clone(wsp, "/" + nodeName, "/" + nodeName, true);
+        
+        Node test2 = s2.getRootNode().getNode(nodeName);
+        test2.setProperty("x", (Value) null);
+        test2.setProperty("x", new String[]{});
+        s2.save();
+        
+        test.update(workspace2);
+
+        try {
+            Value[] values = test.getProperty("x").getValues();
+            assertEquals(0, values.length);
+        } catch (RepositoryException e) {
+            // if we get here, it's a bug, as it is a multi-valued property now
+            // anyway, let's see what happens if we try to read it as a single valued property
+            test.getProperty("x").getValue();
+            // even if that works: it's still a bug
+            throw e;
+        }
+        
+    }
+    
+    private String getAlternativeWorkspaceName() throws RepositoryException {
+        String altWsp = null;
+        String[] wsps = superuser.getWorkspace().getAccessibleWorkspaceNames();
+        if (wsps.length == 1) {
+            superuser.getWorkspace().createWorkspace("tmp");
+            altWsp = "tmp";
+        } else {
+            for (String name : wsps) {
+                if (!name.equals(superuser.getWorkspace().getName())) {
+                    altWsp = name;
+                    break;
+                }
+            }
+        }
+        return altWsp;
+    }
+    
+}
\ No newline at end of file
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/util/TestAll.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/util/TestAll.java
index 91ac690..ce5d030 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/util/TestAll.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/persistence/util/TestAll.java
@@ -27,6 +27,8 @@ public class TestAll extends TestCase {
 
         suite.addTestSuite(HashMapIndexTest.class);
         suite.addTestSuite(BundleBindingTest.class);
+        suite.addTestSuite(NodeCorruptionTest.class);
+        suite.addTestSuite(BundleBindingRandomizedTest.class);
 
         return suite;
     }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/AbstractIndexingTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/AbstractIndexingTest.java
index a29295c..e9fa81c 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/AbstractIndexingTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/AbstractIndexingTest.java
@@ -17,9 +17,13 @@
 package org.apache.jackrabbit.core.query;
 
 import javax.jcr.Node;
+import javax.jcr.RepositoryException;
 import javax.jcr.Session;
+import javax.jcr.query.QueryManager;
+import javax.jcr.query.QueryResult;
 
 import org.apache.jackrabbit.core.TestHelper;
+import org.apache.jackrabbit.core.query.lucene.SearchIndex;
 
 /**
  * <code>AbstractIndexingTest</code> is a base class for all indexing
@@ -60,6 +64,63 @@ public class AbstractIndexingTest extends AbstractQueryTest {
      */
     protected void waitForTextExtractionTasksToFinish() throws Exception {
         TestHelper.waitForTextExtractionTasksToFinish(session);
-        getSearchIndex().flush();
+        flushSearchIndex();
+    }
+
+    protected void flushSearchIndex() throws RepositoryException {
+        SearchIndex si = getSearchIndex();
+        if (si != null) {
+            si.flush();
+        }
+    }
+
+    /**
+     * Returns a reference to the underlying search index.
+     * 
+     * @return the query handler inside the {@link #qm query manager}.
+     */
+    protected SearchIndex getSearchIndex() {
+        if (qm instanceof QueryManagerImpl) {
+            return (SearchIndex) ((QueryManagerImpl) qm).getQueryHandler();
+        }
+        return null;
+    }
+
+    /**
+     * Returns a reference to the session's search index.
+     * 
+     * @return the session's query handler.
+     */
+    protected static SearchIndex getSearchIndex(Session session)
+            throws RepositoryException {
+        QueryManager qm = session.getWorkspace().getQueryManager();
+        if (qm instanceof QueryManagerImpl) {
+            return (SearchIndex) ((QueryManagerImpl) qm).getQueryHandler();
+        }
+        return null;
+    }
+
+    protected QueryResult executeQuery(String statement)
+            throws RepositoryException {
+        flushSearchIndex();
+        return super.executeQuery(statement);
+    }
+
+    protected void executeXPathQuery(String xpath, Node[] nodes)
+            throws RepositoryException {
+        flushSearchIndex();
+        super.executeXPathQuery(xpath, nodes);
+    }
+
+    protected void executeSQLQuery(String sql, Node[] nodes)
+            throws RepositoryException {
+        flushSearchIndex();
+        super.executeSQLQuery(sql, nodes);
+    }
+
+    protected void executeSQL2Query(String statement, Node[] nodes)
+            throws RepositoryException {
+        flushSearchIndex();
+        super.executeSQL2Query(statement, nodes);
     }
 }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/AbstractQueryTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/AbstractQueryTest.java
index e569f10..8b51baa 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/AbstractQueryTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/AbstractQueryTest.java
@@ -38,7 +38,6 @@ import javax.jcr.query.qom.QueryObjectModelFactory;
 
 import org.apache.jackrabbit.commons.JcrUtils;
 import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter;
-import org.apache.jackrabbit.core.query.lucene.SearchIndex;
 import org.apache.jackrabbit.test.AbstractJCRTest;
 
 /**
@@ -143,7 +142,6 @@ public class AbstractQueryTest extends AbstractJCRTest {
      */
     protected void executeXPathQuery(String xpath, Node[] nodes)
             throws RepositoryException {
-        getSearchIndex().flush();
         QueryResult res = qm.createQuery(xpath, Query.XPATH).execute();
         checkResult(res, nodes);
     }
@@ -158,7 +156,6 @@ public class AbstractQueryTest extends AbstractJCRTest {
      */
     protected void executeSQLQuery(String sql, Node[] nodes)
             throws RepositoryException {
-        getSearchIndex().flush();
         QueryResult res = qm.createQuery(sql, Query.SQL).execute();
         checkResult(res, nodes);
     }
@@ -214,7 +211,7 @@ public class AbstractQueryTest extends AbstractJCRTest {
         // check if all expected are in result
         for (Iterator<String> it = expectedPaths.iterator(); it.hasNext();) {
             String path = it.next();
-            assertTrue(path + " is not part of the result set "+ expectedPaths, resultPaths.contains(path));
+            assertTrue(path + " is not part of the result set "+ resultPaths, resultPaths.contains(path));
         }
         // check result does not contain more than expected
         for (Iterator<String> it = resultPaths.iterator(); it.hasNext();) {
@@ -265,17 +262,7 @@ public class AbstractQueryTest extends AbstractJCRTest {
 
     protected void executeSQL2Query(String statement, Node[] nodes)
             throws RepositoryException {
-        getSearchIndex().flush();
         QueryResult res = qm.createQuery(statement, JCR_SQL2).execute();
         checkResult(res, nodes);
     }
-
-    /**
-     * Returns a reference to the underlying search index.
-     *
-     * @return the query handler inside the {@link #qm query manager}.
-     */
-    protected SearchIndex getSearchIndex() {
-        return (SearchIndex) ((QueryManagerImpl) qm).getQueryHandler();
-    }
 }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/ChildAxisQueryTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/ChildAxisQueryTest.java
index 7281cb6..38eeaf2 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/ChildAxisQueryTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/ChildAxisQueryTest.java
@@ -19,12 +19,6 @@ package org.apache.jackrabbit.core.query;
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.util.Calendar;
-
 /**
  * <code>ChildAxisQueryTest</code> tests queries with a child axis in their
  * predicates.
@@ -184,23 +178,6 @@ public class ChildAxisQueryTest extends AbstractQueryTest {
                 new Node[]{testRootNode});
     }
 
-    public void testNtFile() throws RepositoryException, IOException {
-        Node file = testRootNode.addNode(nodeName1, "nt:file");
-        Node resource = file.addNode("jcr:content", "nt:resource");
-        resource.setProperty("jcr:encoding", "UTF-8");
-        resource.setProperty("jcr:mimeType", "text/plain");
-        ByteArrayOutputStream data = new ByteArrayOutputStream();
-        OutputStreamWriter writer = new OutputStreamWriter(data, "UTF-8");
-        writer.write("The quick brown fox jumps over the lazy dog.");
-        writer.close();
-        resource.setProperty("jcr:data", new ByteArrayInputStream(data.toByteArray()));
-        resource.setProperty("jcr:lastModified", Calendar.getInstance());
-
-        testRootNode.save();
-        String xpath = testPath + "/*[jcr:contains(jcr:content, 'lazy')]";
-        executeXPathQuery(xpath, new Node[]{file});
-    }
-
     public void testStarNameTest() throws RepositoryException {
         Node level1 = testRootNode.addNode(nodeName1);
         level1.setProperty(propertyName1, "The quick brown fox jumps over the lazy dog.");
@@ -243,4 +220,16 @@ public class ChildAxisQueryTest extends AbstractQueryTest {
         executeSQLQuery(sql, new Node[] {foo});
     }
 
+    /**
+     * JCR-3337
+     */
+    public void testNotIsDescendantNodeQuery() throws Exception {
+        Node foo = testRootNode.addNode("foo");
+        testRootNode.getSession().save();
+        String sql = "SELECT a.* FROM [nt:base] as a WHERE isdescendantnode(a,'"
+                + testRootNode.getPath()
+                + "') and not isdescendantnode(a,'"
+                + foo.getPath() + "')";
+        executeSQL2Query(sql, new Node[] {foo});
+    }
 }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/DescendantSelfAxisTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/DescendantSelfAxisTest.java
new file mode 100644
index 0000000..405d42b
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/DescendantSelfAxisTest.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.core.query;
+
+import static org.apache.jackrabbit.JcrConstants.*;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+public class DescendantSelfAxisTest extends AbstractQueryTest {
+
+    /**
+     * JCR-3407 CaseTermQuery #rewrite behavior changes
+     */
+    public void testCaseTermQueryNPE() throws RepositoryException {
+        String xpathNPE = "//element(*,nt:unstructured)[fn:lower-case(@jcr:language)='en']//element(*,nt:unstructured)[@jcr:message]/(@jcr:key|@jcr:message)";
+        executeXPathQuery(xpathNPE, new Node[] {});
+    }
+
+    /**
+     * JCR-3401 Wrong results when querying with a DescendantSelfAxisQuery
+     */
+    public void testNodeName() throws RepositoryException {
+        String name = "testNodeName" + System.currentTimeMillis();
+
+        Node foo = testRootNode.addNode("foo", NT_UNSTRUCTURED);
+        foo.addNode("branch1", NT_FOLDER).addNode(name, NT_FOLDER);
+        foo.addNode("branch2", NT_FOLDER).addNode(name, NT_FOLDER);
+        Node bar = testRootNode.addNode(name, NT_UNSTRUCTURED);
+
+        testRootNode.getSession().save();
+
+        executeXPathQuery("//element(*, nt:unstructured)[fn:name() = '" + name
+                + "']", new Node[] { bar });
+        executeXPathQuery("//element(" + name + ", nt:unstructured)",
+                new Node[] { bar });
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/ExcerptTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/ExcerptTest.java
index 5536dfc..3574fd5 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/ExcerptTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/ExcerptTest.java
@@ -239,6 +239,34 @@ public class ExcerptTest extends AbstractQueryTest {
         checkExcerpt(content, expectedExcerpt, jTest);
     }
 
+    /**
+     * test for https://issues.apache.org/jira/browse/JCR-3428
+     * 
+     * when given an incomplete fulltext search token, the excerpt should
+     * highlight the entire matching token
+     * 
+     */
+    public void testEagerMatch() throws RepositoryException {
+        checkExcerpt("lorem ipsum dolor sit amet",
+                "lorem <strong>ipsum</strong> dolor sit amet", "ipsu*");
+    }
+
+    /**
+     * @see #testEagerMatch()
+     */
+    public void testEagerMatch2() throws RepositoryException {
+        checkExcerpt("lorem ipsum dolor sit amet",
+                "<strong>lorem</strong> <strong>ipsum</strong> dolor sit amet",
+                "lorem ipsu*");
+    }
+
+    /**
+     * @see #testEagerMatch()
+     */
+    public void testEagerMatch3() throws RepositoryException {
+        checkExcerpt("lorem ipsum dolor sit amet",
+                "lorem <strong>ipsum</strong> <strong>dolor</strong> sit amet", "ipsu* dolor");
+    }
 
     private void checkExcerpt(String text, String fragmentText, String terms)
             throws RepositoryException {
@@ -273,4 +301,36 @@ public class ExcerptTest extends AbstractQueryTest {
     private String createExcerpt(String fragments) {
         return EXCERPT_START + fragments + EXCERPT_END;
     }
+
+    /**
+     * <p>
+     * Test when there are multiple tokens that match the fulltext search (which
+     * will generate multiple lucene terms for the highlighter to use) in the
+     * repository but not all of them are present in the current property.
+     * </p>
+     * 
+     */
+    public void testMatchMultipleNonMatchingTokens() throws RepositoryException {
+        String text = "lorem ipsum";
+        String fragmentText = "lorem <strong>ipsum</strong>";
+        String terms = "ipsu*";
+
+        String excerpt = createExcerpt(fragmentText);
+
+        // here we'll add more matching garbage data so we have more tokens
+        // passed to the highlighter
+        Node parent = testRootNode.addNode(nodeName1);
+        Node n = parent.addNode("test");
+        n.setProperty("text", text);
+        testRootNode.addNode(nodeName2).setProperty("foo", "ipsuFoo");
+        testRootNode.addNode(nodeName3).setProperty("bar", "ipsuBar");
+        superuser.save();
+        // --
+        String stmt = testPath + "/" + nodeName1 + "//*[jcr:contains(., '"
+                + terms + "')]/rep:excerpt(.)";
+        QueryResult result = executeQuery(stmt);
+        RowIterator rows = result.getRows();
+        assertEquals(1, rows.getSize());
+        assertEquals(excerpt, getExcerpt(rows.nextRow()));
+    }
 }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/FulltextQueryTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/FulltextQueryTest.java
index be47cb9..b4c3c7a 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/FulltextQueryTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/FulltextQueryTest.java
@@ -16,14 +16,12 @@
  */
 package org.apache.jackrabbit.core.query;
 
-import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.Callable;
+import java.util.TreeSet;
 
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
-import javax.jcr.nodetype.NodeType;
 import javax.jcr.query.InvalidQueryException;
 import javax.jcr.query.Query;
 import javax.jcr.query.QueryResult;
@@ -297,19 +295,19 @@ public class FulltextQueryTest extends AbstractQueryTest {
 
         superuser.save();
 
-        List<String> r1 = new ArrayList<String>();
+        TreeSet<String> r1 = new TreeSet<String>();
         QueryResult result = qm.createQuery(testPath + "/*[jcr:contains(@prop1, 'foo') or jcr:contains(@prop2, 'foo') or jcr:contains(@prop3, 'foo')] order by @jcr:score descending", Query.XPATH).execute();
         for (Row r : new RowIterable(result.getRows())) {
             r1.add(r.getPath() + ":" + (int) (r.getScore() * 1000));
         }
 
-        List<String> r2 = new ArrayList<String>();
+        TreeSet<String> r2 = new TreeSet<String>();
         result = qm.createQuery(testPath + "/*[jcr:contains(@prop3, 'foo') or jcr:contains(@prop1, 'foo') or jcr:contains(@prop2, 'foo')] order by @jcr:score descending", Query.XPATH).execute();
         for (Row r : new RowIterable(result.getRows())) {
             r2.add(r.getPath() + ":" + (int) (r.getScore() * 1000));
         }
 
-        List<String> r3 = new ArrayList<String>();
+        TreeSet<String> r3 = new TreeSet<String>();
         result = qm.createQuery(testPath + "/*[jcr:contains(@prop2, 'foo') or jcr:contains(@prop3, 'foo') or jcr:contains(@prop1, 'foo')] order by @jcr:score descending", Query.XPATH).execute();
         for (Row r : new RowIterable(result.getRows())) {
             r3.add(r.getPath() + ":" + (int) (r.getScore() * 1000));
@@ -339,34 +337,6 @@ public class FulltextQueryTest extends AbstractQueryTest {
         assertContainsQuery(statement, match);
     }
 
-    public void testFileContains() throws Exception {
-        assertFileContains(
-                "test.txt", "text/plain", "AE502DBEA2C411DEBD340AD156D89593");
-        assertFileContains(
-                "test.rtf", "text/rtf", "quick brown fox");
-    }
-
-    private void assertFileContains(
-            String name, String type, String... statements) throws Exception {
-        while (testRootNode.hasNode(nodeName1)) {
-            testRootNode.getNode(nodeName1).remove();
-        }
-        Node resource = testRootNode.addNode(nodeName1, NodeType.NT_RESOURCE);
-        resource.setProperty("jcr:mimeType", type);
-        InputStream stream = FulltextQueryTest.class.getResourceAsStream(name);
-        try {
-            resource.setProperty("jcr:data", stream);
-        } finally {
-            stream.close();
-        }
-        testRootNode.save();
-        getSearchIndex().flush();
-
-        for (String statement : statements) {
-            assertContainsQuery(statement, true);
-        }
-    }
-
     private void assertContainsQuery(String statement, boolean match)
             throws InvalidQueryException, RepositoryException {
         StringBuffer stmt = new StringBuffer();
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/IndexFormatVersionTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/IndexFormatVersionTest.java
deleted file mode 100644
index ffa4e4c..0000000
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/IndexFormatVersionTest.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.query;
-
-import org.apache.jackrabbit.test.AbstractJCRTest;
-import org.apache.jackrabbit.core.query.lucene.IndexFormatVersion;
-import org.apache.jackrabbit.core.query.lucene.SearchIndex;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.query.QueryManager;
-
-/**
- * <code>IndexFormatVersionTest</code> checks if the various index format
- * versions are correctly read from the index.
- */
-public class IndexFormatVersionTest extends AbstractJCRTest {
-
-    public void testVersionOne() throws RepositoryException {
-        checkIndexFormatVersion("index-format-v1", IndexFormatVersion.V1);
-    }
-
-    public void testVersionTwo() throws RepositoryException {
-        checkIndexFormatVersion("index-format-v2", IndexFormatVersion.V2);
-    }
-
-    public void testVersionThree() throws RepositoryException {
-        checkIndexFormatVersion("index-format-v3", IndexFormatVersion.V3);
-    }
-
-    private void checkIndexFormatVersion(String wspName,
-                                         IndexFormatVersion version)
-            throws RepositoryException {
-        Session session = getHelper().getSuperuserSession(wspName);
-        try {
-            QueryManager qm = session.getWorkspace().getQueryManager();
-            QueryHandler handler = ((QueryManagerImpl) qm).getQueryHandler();
-            SearchIndex index = (SearchIndex) handler;
-            assertEquals("Wrong index format", version.getVersion(),
-                    index.getIndexFormatVersion().getVersion());
-        } finally {
-            session.logout();
-        }
-    }
-}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/IndexingRuleTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/IndexingRuleTest.java
deleted file mode 100644
index d4d9a6a..0000000
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/IndexingRuleTest.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.query;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Value;
-import javax.jcr.query.QueryResult;
-import javax.jcr.query.Row;
-import javax.jcr.query.RowIterator;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Arrays;
-
-/**
- * <code>IndexingRuleTest</code> performs indexing rule tests.
- */
-public class IndexingRuleTest extends AbstractIndexingTest {
-
-    private static final String NT_UNSTRUCTURED = "nt:unstructured";
-
-    private static final String TEXT = "the quick brown fox jumps over the lazy dog";
-
-    public void testRegexp() throws RepositoryException {
-        Node node1 = testRootNode.addNode(nodeName1, NT_UNSTRUCTURED);
-        node1.setProperty("rule", "regexp");
-        node1.setProperty("Text", "foo");
-        Node node2 = testRootNode.addNode(nodeName2, NT_UNSTRUCTURED);
-        node2.setProperty("rule", "regexp");
-        node2.setProperty("OtherText", "foo");
-        Node node3 = testRootNode.addNode(nodeName3, NT_UNSTRUCTURED);
-        node3.setProperty("rule", "regexp");
-        node3.setProperty("Textle", "foo");
-        testRootNode.save();
-        String stmt = "/jcr:root" + testRootNode.getPath() +
-                "/*[jcr:contains(., 'foo')]";
-        checkResult(executeQuery(stmt), new Node[]{node1, node2});
-    }
-
-    public void testBoost() throws RepositoryException {
-        Node node1 = testRootNode.addNode(nodeName1, NT_UNSTRUCTURED);
-        node1.setProperty("rule", "boost1");
-        node1.setProperty("text", TEXT);
-        Node node2 = testRootNode.addNode(nodeName2, NT_UNSTRUCTURED);
-        node2.setProperty("rule", "boost2");
-        node2.setProperty("text", TEXT);
-        Node node3 = testRootNode.addNode(nodeName3, NT_UNSTRUCTURED);
-        node3.setProperty("rule", "boost3");
-        node3.setProperty("text", TEXT);
-        testRootNode.save();
-        String stmt = "/jcr:root" + testRootNode.getPath() +
-                "/*[jcr:contains(@text, 'quick')] order by @jcr:score descending";
-        List names = new ArrayList();
-        for (NodeIterator it = executeQuery(stmt).getNodes(); it.hasNext(); ) {
-            names.add(it.nextNode().getName());
-        }
-        assertEquals("Wrong sequence or number of results.",
-                Arrays.asList(new String[]{nodeName3, nodeName2, nodeName1}),
-                names);
-    }
-
-    public void testNodeScopeIndex() throws RepositoryException {
-        Node node1 = testRootNode.addNode(nodeName1, NT_UNSTRUCTURED);
-        node1.setProperty("rule", "nsiTrue");
-        node1.setProperty("text", TEXT);
-        Node node2 = testRootNode.addNode(nodeName2, NT_UNSTRUCTURED);
-        node2.setProperty("rule", "nsiFalse");
-        node2.setProperty("text", TEXT);
-        testRootNode.save();
-        String stmt = "/jcr:root" + testRootNode.getPath() +
-                "/*[jcr:contains(., 'quick')]";
-        checkResult(executeQuery(stmt), new Node[]{node1});
-    }
-
-    public void testNodeType() throws RepositoryException {
-        // assumes there is an index-rule for nt:hierarchyNode that
-        // does not include the property jcr:created
-        Node node1 = testRootNode.addNode(nodeName1, "nt:folder");
-        testRootNode.save();
-        String stmt = "/jcr:root" + testRootNode.getPath() +
-                "/*[@" + jcrCreated + " = xs:dateTime('" +
-                node1.getProperty(jcrCreated).getString() + "')]";
-        checkResult(executeQuery(stmt), new Node[]{});
-    }
-
-    public void testUseInExcerpt() throws RepositoryException {
-        Node node = testRootNode.addNode(nodeName1, NT_UNSTRUCTURED);
-        node.setProperty("rule", "excerpt");
-        node.setProperty("title", "Apache Jackrabbit");
-        node.setProperty("text", "Jackrabbit is a JCR implementation");
-        testRootNode.save();
-        String stmt = "/jcr:root" + testRootNode.getPath() +
-                "/*[jcr:contains(., 'jackrabbit implementation')]/rep:excerpt(.)";
-        RowIterator rows = executeQuery(stmt).getRows();
-        assertTrue("No results returned", rows.hasNext());
-        Value excerpt = rows.nextRow().getValue("rep:excerpt(.)");
-        assertNotNull("No excerpt created", excerpt);
-        assertTrue("Title must not be present in excerpt",
-                excerpt.getString().indexOf("Apache") == -1);
-        assertTrue("Missing highlight",
-                excerpt.getString().indexOf("<strong>implementation</strong>") != -1);
-
-        stmt = "/jcr:root" + testRootNode.getPath() +
-                "/*[jcr:contains(., 'apache')]/rep:excerpt(.)";
-        rows = executeQuery(stmt).getRows();
-        assertTrue("No results returned", rows.hasNext());
-        excerpt = rows.nextRow().getValue("rep:excerpt(.)");
-        assertNotNull("No excerpt created", excerpt);
-        assertTrue("Title must not be present in excerpt",
-                excerpt.getString().indexOf("Apache") == -1);
-    }
-
-    public void testExcerptOnExcludedProperty() throws RepositoryException {
-        Node node = testRootNode.addNode(nodeName1, NT_UNSTRUCTURED);
-        node.setProperty("rule", "excerpt");
-        node.setProperty("title", TEXT);
-        testRootNode.save();
-        String stmt = "/jcr:root" + testRootNode.getPath() +
-                "/*[jcr:contains(., 'quick')]/rep:excerpt(.)";
-        QueryResult result = executeQuery(stmt);
-        checkResult(result, new Node[]{node});
-        Value excerpt = result.getRows().nextRow().getValue("rep:excerpt(.)");
-        assertNotNull("No excerpt created", excerpt);
-    }
-
-    public void testUseInExcerptWithAggregate() throws RepositoryException {
-        Node node = testRootNode.addNode(nodeName1, NT_UNSTRUCTURED);
-        node.setProperty("rule", "excerpt");
-        node.setProperty("title", "Apache Jackrabbit");
-        node.setProperty("text", "Jackrabbit is a JCR implementation");
-        Node aggregated = node.addNode("aggregated-node", NT_UNSTRUCTURED);
-        aggregated.setProperty("rule", "excerpt");
-        aggregated.setProperty("title", "Apache Jackrabbit");
-        aggregated.setProperty("text", "Jackrabbit is a JCR implementation");
-        testRootNode.save();
-
-        String stmt = "/jcr:root" + testRootNode.getPath() +
-                "/*[jcr:contains(., 'jackrabbit')]/rep:excerpt(.)";
-        RowIterator rows = executeQuery(stmt).getRows();
-        assertTrue("No results returned", rows.hasNext());
-        Value excerpt;
-        while (rows.hasNext()) {
-            excerpt = rows.nextRow().getValue("rep:excerpt(.)");
-            assertNotNull("No excerpt created", excerpt);
-            assertTrue("Title must not be present in excerpt",
-                    excerpt.getString().indexOf("Apache") == -1);
-            int idx = 0;
-            int numHighlights = 0;
-            for (;;) {
-                idx = excerpt.getString().indexOf("<strong>", idx);
-                if (idx == -1) {
-                    break;
-                }
-                numHighlights++;
-                int endIdx = excerpt.getString().indexOf("</strong>", idx);
-                assertEquals("wrong highlight", "Jackrabbit",
-                        excerpt.getString().substring(idx + "<strong>".length(), endIdx));
-                idx = endIdx;
-            }
-            assertTrue("Missing highlight", numHighlights > 0);
-        }
-
-        stmt = "/jcr:root" + testRootNode.getPath() +
-                "/*[jcr:contains(., 'apache')]/rep:excerpt(.)";
-        rows = executeQuery(stmt).getRows();
-        assertTrue("No results returned", rows.hasNext());
-        excerpt = rows.nextRow().getValue("rep:excerpt(.)");
-        assertNotNull("No excerpt created", excerpt);
-        assertTrue("Title must not be present in excerpt",
-                excerpt.getString().indexOf("Apache") == -1);
-    }
-}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/LargeResultSetTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/LargeResultSetTest.java
deleted file mode 100644
index e4ff025..0000000
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/LargeResultSetTest.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.query;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.query.Query;
-import javax.jcr.query.RowIterator;
-import javax.jcr.query.QueryResult;
-
-import org.apache.jackrabbit.core.query.lucene.SearchIndex;
-
-/**
- * <code>LargeResultSetTest</code>...
- */
-public class LargeResultSetTest extends AbstractQueryTest {
-
-    public void testResultSet() throws RepositoryException {
-        createNodes(testRootNode, 10, 5, 0);
-        superuser.save();
-
-        SearchIndex index = getSearchIndex();
-        int resultFetchSize = index.getResultFetchSize();
-        try {
-            String stmt = testPath + "//*[@" + jcrPrimaryType + "] order by @jcr:score descending";
-
-            // with result fetch size Integer.MAX_VALUE
-            readResult(executeQuery(stmt));
-
-            // with result fetch size 100
-            index.setResultFetchSize(100);
-            readResult(executeQuery(stmt));
-
-            // with 100 limit
-            QueryImpl query = (QueryImpl) qm.createQuery(stmt, Query.XPATH);
-            query.setLimit(100);
-            readResult(query.execute());
-        } finally {
-            index.setResultFetchSize(resultFetchSize);
-        }
-
-        for (NodeIterator it = testRootNode.getNodes(); it.hasNext(); ) {
-            it.nextNode().remove();
-            superuser.save();
-        }
-    }
-
-    private void readResult(QueryResult result) throws RepositoryException {
-        for (RowIterator rows = result.getRows(); rows.hasNext(); ) {
-            rows.nextRow();
-        }
-    }
-
-    private int createNodes(Node n, int nodesPerLevel, int levels, int count)
-            throws RepositoryException {
-        levels--;
-        for (int i = 0; i < nodesPerLevel; i++) {
-            Node child = n.addNode("node" + i);
-            count++;
-            if (count % 10000 == 0) {
-                superuser.save();
-            }
-            if (levels > 0) {
-                count = createNodes(child, nodesPerLevel, levels, count);
-            }
-        }
-        return count;
-    }
-}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/LazyResultSetQueryTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/LazyResultSetQueryTest.java
new file mode 100644
index 0000000..e4c18bc
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/LazyResultSetQueryTest.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.query;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.query.QueryResult;
+import javax.jcr.query.RowIterator;
+
+import org.junit.Ignore;
+
+/**
+ * 
+ * This is meant to test the limits of queries and lazy fetching of nodes
+ * 
+ * @see <a href="https://issues.apache.org/jira/browse/JCR-3477">JCR-3477</a>
+ */
+public class LazyResultSetQueryTest extends AbstractQueryTest {
+
+    @Ignore("JCR-3477")
+    public void testResultSet() throws RepositoryException {
+        int count = createNodes(testRootNode, 10, 5, 0);
+        testRootNode.getSession().save();
+
+        String stmt = testPath + "//*[@" + jcrPrimaryType + "]";
+        // + " order by @jcr:score descending";
+
+        readResult(executeQuery(stmt), count);
+    }
+
+    protected void tearDown() throws Exception {
+        int count = 0;
+        for (NodeIterator it = testRootNode.getNodes(); it.hasNext();) {
+            it.nextNode().remove();
+            count++;
+            if (count % 10000 == 0) {
+                testRootNode.getSession().save();
+            }
+        }
+        testRootNode.getSession().save();
+        super.tearDown();
+    }
+
+    private void readResult(QueryResult result, int count)
+            throws RepositoryException {
+        for (RowIterator rows = result.getRows(); rows.hasNext();) {
+            rows.nextRow();
+            count--;
+        }
+        assertEquals(0, count);
+    }
+
+    private int createNodes(Node n, int nodesPerLevel, int levels, int count)
+            throws RepositoryException {
+        levels--;
+        for (int i = 0; i < nodesPerLevel; i++) {
+            Node child = n.addNode("node" + i);
+            count++;
+            if (count % 10000 == 0) {
+                n.getSession().save();
+            }
+            if (levels > 0) {
+                count = createNodes(child, nodesPerLevel, levels, count);
+            }
+        }
+        return count;
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/LimitAndOffsetTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/LimitAndOffsetTest.java
index da16d46..8448bb4 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/LimitAndOffsetTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/LimitAndOffsetTest.java
@@ -19,12 +19,10 @@ package org.apache.jackrabbit.core.query;
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
 import javax.jcr.RepositoryException;
-import javax.jcr.query.InvalidQueryException;
 import javax.jcr.query.Query;
-import javax.jcr.query.QueryManager;
 import javax.jcr.query.QueryResult;
 
-import org.apache.jackrabbit.core.query.lucene.QueryResultImpl;
+import org.apache.jackrabbit.api.query.JackrabbitQueryResult;
 
 public class LimitAndOffsetTest extends AbstractQueryTest {
 
@@ -32,21 +30,22 @@ public class LimitAndOffsetTest extends AbstractQueryTest {
     private Node node2;
     private Node node3;
 
-    private QueryImpl query;
+    private Query query;
 
     protected void setUp() throws Exception {
         super.setUp();
 
         node1 = testRootNode.addNode("foo");
         node1.setProperty("name", "1");
-        node2 = testRootNode.addNode("foo");
+        node2 = testRootNode.addNode("bar");
         node2.setProperty("name", "2");
-        node3 = testRootNode.addNode("foo");
+        node3 = testRootNode.addNode("baz");
         node3.setProperty("name", "3");
 
-        testRootNode.save();
+        testRootNode.getSession().save();
 
-        query = createXPathQuery("/jcr:root" + testRoot + "/* order by @name");
+        query = qm.createQuery("/jcr:root" + testRoot + "/* order by @name",
+                Query.XPATH);
     }
 
     protected void tearDown() throws Exception {
@@ -57,12 +56,6 @@ public class LimitAndOffsetTest extends AbstractQueryTest {
         super.tearDown();
     }
 
-    private QueryImpl createXPathQuery(String xpath)
-            throws InvalidQueryException, RepositoryException {
-        QueryManager queryManager = superuser.getWorkspace().getQueryManager();
-        return (QueryImpl) queryManager.createQuery(xpath, Query.XPATH);
-    }
-
     protected void checkResult(QueryResult result, Node[] expectedNodes) throws RepositoryException {
         assertEquals(expectedNodes.length, result.getNodes().getSize());
     }
@@ -136,7 +129,9 @@ public class LimitAndOffsetTest extends AbstractQueryTest {
         QueryResult result = query.execute();
         NodeIterator nodes = result.getNodes();
         assertEquals(2, nodes.getSize());
-        assertEquals(3, ((QueryResultImpl) result).getTotalSize());
+        if (result instanceof JackrabbitQueryResult) {
+            assertEquals(3, ((JackrabbitQueryResult) result).getTotalSize());
+        }
 
         // JCR-2684: offset higher than total result => size == 0
         query.setOffset(10);
@@ -144,14 +139,14 @@ public class LimitAndOffsetTest extends AbstractQueryTest {
         nodes = result.getNodes();
         assertFalse(nodes.hasNext());
         assertEquals(0, nodes.getSize());
-        assertEquals(3, ((QueryResultImpl) result).getTotalSize());
+        if (result instanceof JackrabbitQueryResult) {
+            assertEquals(3, ((JackrabbitQueryResult) result).getTotalSize());
+        }
 
         query.setOffset(1);
         query.setLimit(1);
         result = query.execute();
         nodes = result.getNodes();
         assertEquals(1, nodes.getSize());
-        assertEquals(3, ((QueryResultImpl) result).getTotalSize());
     }
-
 }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/MixinTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/MixinTest.java
index ed0701f..b2c2286 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/MixinTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/MixinTest.java
@@ -23,8 +23,8 @@ import java.util.Calendar;
 
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeTypeManager;
 
-import org.apache.jackrabbit.api.JackrabbitNodeTypeManager;
 import org.apache.jackrabbit.commons.cnd.CndImporter;
 
 /**
@@ -36,8 +36,7 @@ public class MixinTest extends AbstractQueryTest {
     protected void setUp() throws Exception {
         super.setUp();
 
-        JackrabbitNodeTypeManager manager = (JackrabbitNodeTypeManager)
-            superuser.getWorkspace().getNodeTypeManager();
+        NodeTypeManager manager = superuser.getWorkspace().getNodeTypeManager();
         if (!manager.hasNodeType("test:mimeType")) {
             String cnd =
                 "<test='http://www.apache.org/jackrabbit/test'>\n"
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/OrderByTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/OrderByTest.java
index c7c09d6..45eefd9 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/OrderByTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/OrderByTest.java
@@ -32,7 +32,7 @@ import java.util.List;
 /**
  * Tests queries with order by.
  */
-public class OrderByTest extends AbstractIndexingTest {
+public class OrderByTest extends AbstractQueryTest {
 
     public void testOrderByScore() throws RepositoryException {
         Node n1 = testRootNode.addNode("node1");
@@ -50,12 +50,12 @@ public class OrderByTest extends AbstractIndexingTest {
 
         String sql = "SELECT value FROM nt:unstructured WHERE " +
                 "jcr:path LIKE '" + testRoot + "/%' ORDER BY jcr:score, value";
-        Query q = session.getWorkspace().getQueryManager().createQuery(sql, Query.SQL);
+        Query q = qm.createQuery(sql, Query.SQL);
         QueryResult result = q.execute();
         checkResult(result, 3);
 
         String xpath = "/" + testRoot + "/*[@jcr:primaryType='nt:unstructured'] order by jcr:score(), @value";
-        q = session.getWorkspace().getQueryManager().createQuery(xpath, Query.XPATH);
+        q = qm.createQuery(xpath, Query.XPATH);
         result = q.execute();
         checkResult(result, 3);
     }
@@ -104,7 +104,7 @@ public class OrderByTest extends AbstractIndexingTest {
         testRootNode.save();
 
         String xpath = "/" + testRoot + "/*[@jcr:primaryType='nt:unstructured'] order by fn:upper-case(@text)";
-        Query q = session.getWorkspace().getQueryManager().createQuery(xpath, Query.XPATH);
+        Query q = qm.createQuery(xpath, Query.XPATH);
         QueryResult result = q.execute();
         checkResult(result, new Node[]{n1, n2, n3});
     }
@@ -121,7 +121,7 @@ public class OrderByTest extends AbstractIndexingTest {
         testRootNode.save();
 
         String xpath = "/" + testRoot + "/*[@jcr:primaryType='nt:unstructured'] order by fn:lower-case(@text)";
-        Query q = session.getWorkspace().getQueryManager().createQuery(xpath, Query.XPATH);
+        Query q = qm.createQuery(xpath, Query.XPATH);
         QueryResult result = q.execute();
         checkResult(result, new Node[]{n1, n2, n3});
     }
@@ -164,7 +164,7 @@ public class OrderByTest extends AbstractIndexingTest {
         n1.addNode("a").addNode("b"); // no property
         Node n2 = testRootNode.addNode("node2");
         n2.addNode("a").addNode("b").addNode("c").setProperty("prop", "a");
-        Node n3 = testRootNode.addNode("node2");
+        Node n3 = testRootNode.addNode("node3");
         n3.addNode("a").addNode("b").addNode("c").setProperty("prop", "b");
         testRootNode.save();
 
@@ -213,31 +213,31 @@ public class OrderByTest extends AbstractIndexingTest {
     //------------------------------< helper >----------------------------------
 
     private Value getValue(String value) throws RepositoryException {
-        return session.getValueFactory().createValue(value);
+        return superuser.getValueFactory().createValue(value);
     }
 
     private Value getValue(long value) throws RepositoryException {
-        return session.getValueFactory().createValue(value);
+        return superuser.getValueFactory().createValue(value);
     }
 
     private Value getValue(double value) throws RepositoryException {
-        return session.getValueFactory().createValue(value);
+        return superuser.getValueFactory().createValue(value);
     }
 
     private Value getValue(boolean value) throws RepositoryException {
-        return session.getValueFactory().createValue(value);
+        return superuser.getValueFactory().createValue(value);
     }
 
     private Value getValue(Calendar value) throws RepositoryException {
-        return session.getValueFactory().createValue(value);
+        return superuser.getValueFactory().createValue(value);
     }
 
     private Value getNameValue(String value) throws RepositoryException {
-        return session.getValueFactory().createValue(value, PropertyType.NAME);
+        return superuser.getValueFactory().createValue(value, PropertyType.NAME);
     }
 
     private Value getPathValue(String value) throws RepositoryException {
-        return session.getValueFactory().createValue(value, PropertyType.PATH);
+        return superuser.getValueFactory().createValue(value, PropertyType.PATH);
     }
 
     /**
@@ -251,7 +251,7 @@ public class OrderByTest extends AbstractIndexingTest {
         // child/prop is part of the test indexing configuration,
         // this will use SimpleScoreDocComparator internally
         checkChildAxis(values, "child", "property");
-        cleanUpTestRoot(session);
+        cleanUpTestRoot(superuser);
         // c/p is not in the indexing configuration,
         // this will use RelPathScoreDocComparator internally
         checkChildAxis(values, "c", "p");
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2NodeLocalNameTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2NodeLocalNameTest.java
index fc1120f..b9f5e17 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2NodeLocalNameTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2NodeLocalNameTest.java
@@ -22,7 +22,7 @@ import org.apache.jackrabbit.commons.JcrUtils;
 
 /**
  * Test case for Node LocalName queries with JCR_SQL2
- * 
+ *
  * Inspired by <a
  * href="https://issues.apache.org/jira/browse/JCR-2956">JCR-2956</a>
  */
@@ -118,4 +118,22 @@ public class SQL2NodeLocalNameTest extends AbstractQueryTest {
                 executeSQL2Query("SELECT * FROM [nt:base] as NODE WHERE LOWER(name(NODE)) like 'sql2nodelocalnametest%'"),
                 2);
     }
+
+    /**
+     * test for JCR-3398
+     */
+    public void testLowerLocalNameOrContains() throws Exception {
+        checkResult(
+                executeSQL2Query("SELECT * FROM [nt:base] as NODE WHERE LOWER(localname(NODE)) like 'sql2nodelocalnametest%' OR contains(NODE.*, 'sql2nodelocalnametest')"),
+                2);
+    }
+
+    /**
+     * test for JCR-3398
+     */
+    public void testUpperLocalNameOrContains() throws Exception {
+        checkResult(
+                executeSQL2Query("SELECT * FROM [nt:base] as NODE WHERE UPPER(localname(NODE)) like 'SQL2NODELOCALNAMETEST%' OR contains(NODE.*, 'SQL2NODELOCALNAMETEST')"),
+                2);
+    }
 }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OrderByTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OrderByTest.java
index 8bda83f..7889b87 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OrderByTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OrderByTest.java
@@ -20,6 +20,8 @@ import javax.jcr.Node;
 import javax.jcr.NodeIterator;
 import javax.jcr.RepositoryException;
 import javax.jcr.query.QueryResult;
+import javax.jcr.query.Row;
+import javax.jcr.query.RowIterator;
 
 import org.apache.jackrabbit.commons.JcrUtils;
 
@@ -68,9 +70,21 @@ public class SQL2OrderByTest extends AbstractQueryTest {
         testRootNode.getSession().save();
 
         QueryResult qr = executeSQL2Query("SELECT * FROM [nt:base] WHERE ISCHILDNODE(["
-                + testRoot + "]) ORDER BY [jcr:score]");
-        checkSeq(qr, new Node[] { n1, n2, n3 });
-
+                + testRoot + "]) ORDER BY SCORE()");
+        RowIterator rows = qr.getRows();
+
+        long size = rows.getSize();
+        assertTrue(size == 3 || size == -1);
+        size = 0;
+
+        double score = Double.MIN_VALUE;
+        while (rows.hasNext()) {
+            double nextScore = rows.nextRow().getScore();
+            assertTrue(nextScore >= score);
+            score = nextScore;
+            size++;
+        }
+        assertEquals(3, size);
     }
 
     /**
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OuterJoinTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OuterJoinTest.java
index 47810d2..3a461d8 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OuterJoinTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2OuterJoinTest.java
@@ -48,7 +48,7 @@ public class SQL2OuterJoinTest extends AbstractQueryTest {
             StringBuilder defs = new StringBuilder();
             defs.append("[test:SamplePage]\n");
             defs.append("  - n1prop1\n");
-            defs.append("  + node2\n");
+            defs.append("  + * (nt:base) = nt:unstructured \n");
             defs.append("[test:SampleContent]\n");
             defs.append("  - n2prop1");
             Reader cndReader = new InputStreamReader(new ByteArrayInputStream(
@@ -58,10 +58,8 @@ public class SQL2OuterJoinTest extends AbstractQueryTest {
 
         Node n1 = testRootNode.addNode("node1", "test:SamplePage");
         n1.setProperty("n1prop1", "page1");
-
         n2 = n1.addNode("node2", "test:SampleContent");
         n2.setProperty("n2prop1", "content1");
-
         testRootNode.getSession().save();
     }
 
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2QueryResultTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2QueryResultTest.java
index b3e486b..b8e4f60 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2QueryResultTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2QueryResultTest.java
@@ -27,7 +27,9 @@ import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 import javax.jcr.nodetype.NodeTypeManager;
 import javax.jcr.query.QueryResult;
+import javax.jcr.query.Row;
 
+import org.apache.jackrabbit.commons.JcrUtils;
 import org.apache.jackrabbit.commons.cnd.CndImporter;
 
 /**
@@ -112,6 +114,10 @@ public class SQL2QueryResultTest extends AbstractQueryTest {
                     .getColumnNames())));
             assertTrue("Got unexpected columns: " + expectedCols,
                     expectedCols.isEmpty());
+            for (Row row : JcrUtils.getRows(r)) {
+                assertNotNull(row.getValues());
+                assertEquals(expected, row.getValues().length);
+            }
         }
     }
 }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2TooManyClausesTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2TooManyClausesTest.java
index 9b33a7f..184841f 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2TooManyClausesTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQL2TooManyClausesTest.java
@@ -35,7 +35,7 @@ public class SQL2TooManyClausesTest extends AbstractQueryTest {
 
     private Node girls;
 
-    private final int nodes = 1100;
+    private final int nodes = 3300;
 
     @Override
     protected void setUp() throws Exception {
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQLTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQLTest.java
index 982a496..048c661 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQLTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SQLTest.java
@@ -51,7 +51,12 @@ public class SQLTest extends AbstractQueryTest {
         checkResult(result, 1);
     }
 
-    public void testFulltextComplex() throws Exception {
+    /**
+     * I'm not sure what this test is supposed to verify. It only works because
+     * the sql parser is not strict enough and the column values are never
+     * actually checked
+     */
+    public void _testFulltextComplex() throws Exception {
         Node foo = testRootNode.addNode("foo");
         foo.setProperty("mytext", new String[]{"the quick brown fox jumps over the lazy dog."});
 
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/ShareableNodeTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/ShareableNodeTest.java
index aea9f29..af4cf12 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/ShareableNodeTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/ShareableNodeTest.java
@@ -16,12 +16,10 @@
  */
 package org.apache.jackrabbit.core.query;
 
-import javax.jcr.RepositoryException;
 import javax.jcr.Node;
-import javax.jcr.Workspace;
 import javax.jcr.NodeIterator;
-
-import org.apache.jackrabbit.core.NodeImpl;
+import javax.jcr.RepositoryException;
+import javax.jcr.Workspace;
 
 /**
  * <code>ShareableNodeTest</code> performs query tests with shareable nodes.
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SimpleQueryTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SimpleQueryTest.java
index 6367d4e..3855c96 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SimpleQueryTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SimpleQueryTest.java
@@ -20,6 +20,8 @@ import javax.jcr.Node;
 import javax.jcr.Value;
 import javax.jcr.query.Query;
 import javax.jcr.query.QueryResult;
+
+import java.math.BigDecimal;
 import java.util.Calendar;
 
 /**
@@ -138,6 +140,38 @@ public class SimpleQueryTest extends AbstractQueryTest {
         checkResult(result, 3);
     }
 
+    public void testBigDecimalField() throws Exception {
+        Node n1 = testRootNode.addNode("node1");
+        n1.setProperty(
+                "value",
+                superuser.getValueFactory().createValue(
+                        new BigDecimal(1.9928375d)));
+        Node n2 = testRootNode.addNode("node2");
+        n2.setProperty("value",
+                superuser.getValueFactory().createValue(new BigDecimal(0.0d)));
+        Node n3 = testRootNode.addNode("node3");
+        n3.setProperty(
+                "value",
+                superuser.getValueFactory().createValue(
+                        new BigDecimal(-1.42982475d)));
+        testRootNode.getSession().save();
+
+        String baseSql2 = "SELECT * FROM [nt:base] WHERE ISCHILDNODE(["
+                + testRoot + "]) AND value";
+        checkResult(qm.createQuery(baseSql2 + " > 0.1", Query.JCR_SQL2)
+                .execute(), new Node[] { n1 });
+        checkResult(
+                qm.createQuery(baseSql2 + " = CAST('0' AS DECIMAL)",
+                        Query.JCR_SQL2).execute(), new Node[] { n2 });
+        checkResult(
+                qm.createQuery(baseSql2 + " = CAST(0 AS DECIMAL)",
+                        Query.JCR_SQL2).execute(), new Node[] { n2 });
+        checkResult(qm.createQuery(baseSql2 + " > -0.1", Query.JCR_SQL2)
+                .execute(), new Node[] { n1, n2 });
+        checkResult(qm.createQuery(baseSql2 + " > -1.5", Query.JCR_SQL2)
+                .execute(), new Node[] { n1, n2, n3 });
+    }
+
     public void testLongField() throws Exception {
         Node n = testRootNode.addNode("node1");
         n.setProperty("value", new Value[]{superuser.getValueFactory().createValue(1)});
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SynonymProviderTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SynonymProviderTest.java
deleted file mode 100644
index bd15511..0000000
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/SynonymProviderTest.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.query;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Node;
-
-/**
- * <code>SynonymProviderTest</code> contains test cases for the
- * <code>PropertiesSynonymProvider</code> class.
- * This test assumes that the following synonyms are defined:
- * <ul>
- * <li>quick <-> fast</li>
- * <li>sluggish <-> lazy</li>
- * <li>ASF <-> Apache Software Foundation</li>
- * </ul>
- */
-public class SynonymProviderTest extends AbstractQueryTest {
-
-    public void testSynonyms() throws RepositoryException {
-        Node n = testRootNode.addNode(nodeName1);
-        n.setProperty(propertyName1, "The quick brown fox jumps over the lazy dog.");
-        testRootNode.save();
-        executeXPathQuery(testPath + "//*[jcr:contains(., '~fast')]", new Node[]{n});
-        executeXPathQuery(testPath + "//*[jcr:contains(., '~Fast')]", new Node[]{n});
-        executeXPathQuery(testPath + "//*[jcr:contains(., '~quick')]", new Node[]{n});
-        executeXPathQuery(testPath + "//*[jcr:contains(., '~sluggish')]", new Node[]{n});
-        executeXPathQuery(testPath + "//*[jcr:contains(., '~sluGGish')]", new Node[]{n});
-        executeXPathQuery(testPath + "//*[jcr:contains(., '~lazy')]", new Node[]{n});
-        // check term which is not in the synonym provider
-        executeXPathQuery(testPath + "//*[jcr:contains(., '~brown')]", new Node[]{n});
-    }
-
-    public void testPhrase() throws RepositoryException {
-        Node n = testRootNode.addNode(nodeName1);
-        n.setProperty(propertyName1, "Licensed to the Apache Software Foundation ...");
-        testRootNode.save();
-        executeXPathQuery(testPath + "//*[jcr:contains(., '~ASF')]", new Node[]{n});
-        executeXPathQuery(testPath + "//*[jcr:contains(., '~asf')]", new Node[]{n});
-        executeXPathQuery(testPath + "//*[jcr:contains(., 'asf')]", new Node[]{});
-    }
-
-    public void disabled_testReload() throws RepositoryException, InterruptedException {
-        for (int i = 0; i < 60; i++) {
-            Thread.sleep(1 * 1000);
-            executeXPathQuery(testPath + "//*[jcr:contains(., '~asf')]", new Node[]{});
-        }
-    }
-}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java
index 2c0a286..aa1b6af 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/TestAll.java
@@ -16,13 +16,12 @@
  */
 package org.apache.jackrabbit.core.query;
 
-import org.apache.jackrabbit.core.query.lucene.hits.ArrayHitsTest;
-import org.apache.jackrabbit.test.ConcurrentTestSuite;
-
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
 
+import org.apache.jackrabbit.test.ConcurrentTestSuite;
+
 /**
  * Test suite that includes all testcases for the Search module.
  */
@@ -55,11 +54,7 @@ public class TestAll extends TestCase {
         suite.addTestSuite(QueryResultTest.class);
         suite.addTestSuite(FnNameQueryTest.class);
         suite.addTestSuite(PathQueryNodeTest.class);
-        suite.addTestSuite(SynonymProviderTest.class);
-        suite.addTestSuite(ArrayHitsTest.class);
         suite.addTestSuite(ExcerptTest.class);
-        suite.addTestSuite(IndexFormatVersionTest.class);
-        suite.addTestSuite(IndexingRuleTest.class);
         suite.addTestSuite(ShareableNodeTest.class);
         suite.addTestSuite(ParentNodeTest.class);
         suite.addTestSuite(SimilarQueryTest.class);
@@ -72,6 +67,7 @@ public class TestAll extends TestCase {
         suite.addTestSuite(LimitedAccessQueryTest.class);
         suite.addTestSuite(SQL2OffsetLimitTest.class);
         suite.addTestSuite(SQL2OrderByTest.class);
+        suite.addTestSuite(DescendantSelfAxisTest.class);
 
         return suite;
     }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/ChainedTermEnumTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/ChainedTermEnumTest.java
index 24f3744..6278315 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/ChainedTermEnumTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/ChainedTermEnumTest.java
@@ -22,6 +22,7 @@ import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.TermEnum;
 import org.apache.lucene.store.Directory;
@@ -61,21 +62,28 @@ public class ChainedTermEnumTest extends TestCase {
     protected TermEnum createTermEnum(String prefix, int numTerms)
             throws IOException {
         Directory dir = new RAMDirectory();
-        IndexWriter writer = new IndexWriter(dir, new StandardAnalyzer(Version.LUCENE_24),
-                true, IndexWriter.MaxFieldLength.UNLIMITED);
-        for (int i = 0; i < numTerms; i++) {
-            Document doc = new Document();
-            doc.add(new Field("field", true, prefix + i, Field.Store.NO,
-                    Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO));
-            writer.addDocument(doc);
+        IndexWriter writer = new IndexWriter(dir, new IndexWriterConfig(
+                Version.LUCENE_36, new StandardAnalyzer(Version.LUCENE_36)));
+        try {
+            for (int i = 0; i < numTerms; i++) {
+                Document doc = new Document();
+                doc.add(new Field("field", true, prefix + i, Field.Store.NO,
+                        Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO));
+                writer.addDocument(doc);
+            }
+        } finally {
+            writer.close();
         }
-        writer.close();
-        IndexReader reader = IndexReader.open(dir, false);
-        TermEnum terms = reader.terms();
-        if (terms.term() == null) {
-            // position at first term
-            terms.next();
+        IndexReader reader = IndexReader.open(dir);
+        try {
+            TermEnum terms = reader.terms();
+            if (terms.term() == null) {
+                // position at first term
+                terms.next();
+            }
+            return terms;
+        } finally {
+            reader.close();
         }
-        return terms;
     }
 }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/DecimalConvertTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/DecimalConvertTest.java
index 761f8e8..5df6752 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/DecimalConvertTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/DecimalConvertTest.java
@@ -28,13 +28,13 @@ import org.apache.jackrabbit.test.JUnitTest;
  * Tests converting BigDecimal to String and back.
  */
 public class DecimalConvertTest extends JUnitTest {
-    
+
     public void testCommon() {
         // System.out.println(DecimalField.decimalToString(new BigDecimal(0)));
         // System.out.println(DecimalField.decimalToString(new BigDecimal(2)));
         // System.out.println(DecimalField.decimalToString(new BigDecimal(120)));
         // System.out.println(DecimalField.decimalToString(new BigDecimal(-1)));
-        
+
         ArrayList<BigDecimal> list = new ArrayList<BigDecimal>();
         list.add(BigDecimal.ZERO);
         list.add(BigDecimal.ONE);
@@ -56,8 +56,22 @@ public class DecimalConvertTest extends JUnitTest {
         list.add(new BigDecimal("-1.23E-10"));
         testWithList(list);
     }
-    
+
+    public void testMinimumScale() {
+        if (true) { // JCR-3834
+            return;
+        }
+        final BigDecimal d1 = new BigDecimal(BigInteger.ONE, Integer.MIN_VALUE);
+        final BigDecimal d2 = new BigDecimal(BigInteger.ONE, Integer.MIN_VALUE+1);
+        final String s1 = DecimalField.decimalToString(d1);
+        final String s2 = DecimalField.decimalToString(d2);
+        assertEquals(Math.signum(d1.compareTo(d2)), Math.signum(s1.compareTo(s2)));
+    }
+
     public void testRandomized() {
+        if (true) { // JCR-3834
+            return;
+        }
         ArrayList<BigDecimal> list = new ArrayList<BigDecimal>();
         list.add(BigDecimal.ZERO);
         list.add(BigDecimal.ONE);
@@ -86,7 +100,7 @@ public class DecimalConvertTest extends JUnitTest {
         }
         testWithList(list);
     }
-    
+
     private void testWithList(ArrayList<BigDecimal> list) {
         // add negative values
         for (BigDecimal d : new ArrayList<BigDecimal>(list)) {
@@ -98,7 +112,7 @@ public class DecimalConvertTest extends JUnitTest {
         for (BigDecimal d : list) {
             String s = DecimalField.decimalToString(d);
             if (lastDecimal != null) {
-                int compDecimal = lastDecimal.compareTo(d);
+                int compDecimal = (int) Math.signum(lastDecimal.compareTo(d));
                 int compString = (int) Math.signum(lastString.compareTo(s));
                 if (compDecimal != compString) {
                     assertEquals(compDecimal, compString);
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexFormatVersionTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexFormatVersionTest.java
new file mode 100644
index 0000000..467f4e6
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexFormatVersionTest.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.query.lucene;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.core.query.AbstractIndexingTest;
+
+/**
+ * <code>IndexFormatVersionTest</code> checks if the various index format
+ * versions are correctly read from the index.
+ */
+public class IndexFormatVersionTest extends AbstractIndexingTest {
+
+    public void testVersionOne() throws RepositoryException {
+        checkIndexFormatVersion("index-format-v1", IndexFormatVersion.V1);
+    }
+
+    public void testVersionTwo() throws RepositoryException {
+        checkIndexFormatVersion("index-format-v2", IndexFormatVersion.V2);
+    }
+
+    public void testVersionThree() throws RepositoryException {
+        checkIndexFormatVersion("index-format-v3", IndexFormatVersion.V3);
+    }
+
+    private void checkIndexFormatVersion(String wspName,
+                                         IndexFormatVersion version)
+            throws RepositoryException {
+        Session session = getHelper().getSuperuserSession(wspName);
+        try {
+            SearchIndex index = getSearchIndex(session);
+            assertEquals("Wrong index format", version.getVersion(),
+                    index.getIndexFormatVersion().getVersion());
+        } finally {
+            session.logout();
+        }
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexInfosTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexInfosTest.java
new file mode 100644
index 0000000..31926f1
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexInfosTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.query.lucene;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+
+import junit.framework.TestCase;
+
+/**
+ * <code>IndexInfosTest</code> check if <code>IndexInfos</code> can deal with
+ * invalid info files. See also JCR-3299.
+ */
+public class IndexInfosTest extends TestCase {
+
+    private static final File TEST_DIR = new File(new File("target"), "indexInfosTest");
+
+    private Directory dir;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        FileUtils.deleteDirectory(TEST_DIR);
+        TEST_DIR.mkdirs();
+        dir = FSDirectory.open(TEST_DIR);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        dir.close();
+        FileUtils.deleteDirectory(TEST_DIR);
+        super.tearDown();
+    }
+
+    public void testEmptyIndexesFile() throws IOException {
+        // creates initial generation of infos
+        IndexInfos infos = new IndexInfos(dir, "indexes");
+        long initialGeneration = infos.getGeneration();
+
+        // create second generation
+        infos.addName("index1", 23432);
+        infos.write();
+
+        // replace second generation file with an empty one
+        String fileName = infos.getFileName();
+        dir.deleteFile(fileName);
+        dir.createOutput(fileName).close();
+
+        new IndexHistory(dir, Integer.MAX_VALUE); // must succeed
+
+        // read index infos again
+        infos = new IndexInfos(dir, "indexes");
+        assertEquals("must read initial generation", initialGeneration, infos.getGeneration());
+
+        // create new generation
+        infos.addName("index1", 39854);
+        infos.write(); // must succeed
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexMigrationTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexMigrationTest.java
index d4b9c35..a87e0ed 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexMigrationTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexMigrationTest.java
@@ -50,7 +50,7 @@ public class IndexMigrationTest extends TestCase {
         DirectoryManager dirMgr = new RAMDirectoryManager();
 
         PersistentIndex idx = new PersistentIndex("index",
-                new StandardAnalyzer(Version.LUCENE_24), Similarity.getDefault(),
+                new StandardAnalyzer(Version.LUCENE_36), Similarity.getDefault(),
                 new DocNumberCache(100),
                 new IndexingQueue(new IndexingQueueStore(new RAMDirectory())),
                 dirMgr, 0);
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexingAggregateTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexingAggregateTest.java
index 10c5e0d..6152b7d 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexingAggregateTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexingAggregateTest.java
@@ -20,6 +20,8 @@ import javax.jcr.RepositoryException;
 import javax.jcr.Node;
 import javax.jcr.query.Query;
 
+import org.apache.jackrabbit.core.query.AbstractIndexingTest;
+
 import java.io.ByteArrayOutputStream;
 import java.io.Writer;
 import java.io.OutputStreamWriter;
@@ -30,7 +32,6 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
 
-import org.apache.jackrabbit.core.query.AbstractIndexingTest;
 
 /**
  * <code>IndexingAggregateTest</code> checks if the nt:file nt:resource
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationImplTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationImplTest.java
index 8ec6aeb..ca00ea2 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationImplTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationImplTest.java
@@ -27,7 +27,7 @@ import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
 
 import org.apache.jackrabbit.core.id.NodeId;
-import org.apache.jackrabbit.core.query.AbstractQueryTest;
+import org.apache.jackrabbit.core.query.AbstractIndexingTest;
 import org.apache.jackrabbit.core.state.NodeState;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
@@ -38,7 +38,7 @@ import org.xml.sax.SAXException;
 /**
  * <code>IndexingConfigurationImplTest</code>...
  */
-public class IndexingConfigurationImplTest extends AbstractQueryTest {
+public class IndexingConfigurationImplTest extends AbstractIndexingTest {
 
     private static final Name FOO = NameFactoryImpl.getInstance().create("", "foo");
 
@@ -49,7 +49,7 @@ public class IndexingConfigurationImplTest extends AbstractQueryTest {
         super.setUp();
         Node n = testRootNode.addNode(nodeName1, ntUnstructured);
         n.addMixin(mixReferenceable);
-        superuser.save();
+        session.save();
         nState = (NodeState) getSearchIndex().getContext().getItemStateManager().getItemState(
                 new NodeId(n.getIdentifier()));
     }
@@ -75,7 +75,7 @@ public class IndexingConfigurationImplTest extends AbstractQueryTest {
     public void testAddNodeTypeToRegistry() throws Exception {
         IndexingConfiguration config = createConfig("config4");
         // add node type
-        NodeTypeManager ntMgr = superuser.getWorkspace().getNodeTypeManager();
+        NodeTypeManager ntMgr = session.getWorkspace().getNodeTypeManager();
         String baseName = "indexingTextNodeType";
         int i = 0;
         String nt;
@@ -89,7 +89,7 @@ public class IndexingConfigurationImplTest extends AbstractQueryTest {
         ntMgr.registerNodeType(ntTemplate, false);
         // create node
         Node n = testRootNode.addNode(nodeName2, nt);
-        superuser.save();
+        session.save();
         // get state
         NodeState state = (NodeState) getSearchIndex().getContext().getItemStateManager().getItemState(
                 new NodeId(n.getIdentifier()));
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexingRuleTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexingRuleTest.java
new file mode 100644
index 0000000..3cac486
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/IndexingRuleTest.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.query.lucene;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.query.QueryResult;
+import javax.jcr.query.RowIterator;
+
+import org.apache.jackrabbit.core.query.AbstractIndexingTest;
+
+
+/**
+ * <code>IndexingRuleTest</code> performs indexing rule tests.
+ */
+public class IndexingRuleTest extends AbstractIndexingTest {
+
+    private static final String NT_UNSTRUCTURED = "nt:unstructured";
+
+    private static final String TEXT = "the quick brown fox jumps over the lazy dog";
+
+    public void testRegexp() throws RepositoryException {
+        Node node1 = testRootNode.addNode(nodeName1, NT_UNSTRUCTURED);
+        node1.setProperty("rule", "regexp");
+        node1.setProperty("Text", "foo");
+        Node node2 = testRootNode.addNode(nodeName2, NT_UNSTRUCTURED);
+        node2.setProperty("rule", "regexp");
+        node2.setProperty("OtherText", "foo");
+        Node node3 = testRootNode.addNode(nodeName3, NT_UNSTRUCTURED);
+        node3.setProperty("rule", "regexp");
+        node3.setProperty("Textle", "foo");
+        testRootNode.save();
+        String stmt = "/jcr:root" + testRootNode.getPath() +
+                "/*[jcr:contains(., 'foo')]";
+        checkResult(executeQuery(stmt), new Node[]{node1, node2});
+    }
+
+    public void testBoost() throws RepositoryException {
+        Node node1 = testRootNode.addNode(nodeName1, NT_UNSTRUCTURED);
+        node1.setProperty("rule", "boost1");
+        node1.setProperty("text", TEXT);
+        Node node2 = testRootNode.addNode(nodeName2, NT_UNSTRUCTURED);
+        node2.setProperty("rule", "boost2");
+        node2.setProperty("text", TEXT);
+        Node node3 = testRootNode.addNode(nodeName3, NT_UNSTRUCTURED);
+        node3.setProperty("rule", "boost3");
+        node3.setProperty("text", TEXT);
+        testRootNode.getSession().save();
+        String stmt = "/jcr:root"
+                + testRootNode.getPath()
+                + "/*[jcr:contains(@text, 'quick')] order by @jcr:score descending";
+        executeXPathQuery(stmt, new Node[] { node3, node2, node1 });
+    }
+
+    public void testNodeScopeIndex() throws RepositoryException {
+        Node node1 = testRootNode.addNode(nodeName1, NT_UNSTRUCTURED);
+        node1.setProperty("rule", "nsiTrue");
+        node1.setProperty("text", TEXT);
+        Node node2 = testRootNode.addNode(nodeName2, NT_UNSTRUCTURED);
+        node2.setProperty("rule", "nsiFalse");
+        node2.setProperty("text", TEXT);
+        testRootNode.save();
+        String stmt = "/jcr:root" + testRootNode.getPath() +
+                "/*[jcr:contains(., 'quick')]";
+        checkResult(executeQuery(stmt), new Node[]{node1});
+    }
+
+    public void testNodeType() throws RepositoryException {
+        // assumes there is an index-rule for nt:hierarchyNode that
+        // does not include the property jcr:created
+        Node node1 = testRootNode.addNode(nodeName1, "nt:folder");
+        testRootNode.save();
+        String stmt = "/jcr:root" + testRootNode.getPath() +
+                "/*[@" + jcrCreated + " = xs:dateTime('" +
+                node1.getProperty(jcrCreated).getString() + "')]";
+        checkResult(executeQuery(stmt), new Node[]{});
+    }
+
+    public void testUseInExcerpt() throws RepositoryException {
+        Node node = testRootNode.addNode(nodeName1, NT_UNSTRUCTURED);
+        node.setProperty("rule", "excerpt");
+        node.setProperty("title", "Apache Jackrabbit");
+        // the value below is for testing https://issues.apache.org/jira/browse/JCR-3610
+        node.setProperty("foo", "<some>markup</some>");
+        node.setProperty("text", "Jackrabbit is a JCR implementation");
+        testRootNode.save();
+        String stmt = "/jcr:root" + testRootNode.getPath() +
+                "/*[jcr:contains(., 'jackrabbit implementation')]/rep:excerpt(.)";
+        RowIterator rows = executeQuery(stmt).getRows();
+        assertTrue("No results returned", rows.hasNext());
+        Value excerpt = rows.nextRow().getValue("rep:excerpt(.)");
+        assertNotNull("No excerpt created", excerpt);
+        assertTrue("Title must not be present in excerpt",
+                excerpt.getString().indexOf("Apache") == -1);
+        assertTrue("Missing highlight",
+                excerpt.getString().indexOf("<strong>implementation</strong>") != -1);
+
+        stmt = "/jcr:root" + testRootNode.getPath() +
+                "/*[jcr:contains(., 'apache')]/rep:excerpt(.)";
+        rows = executeQuery(stmt).getRows();
+        assertTrue("No results returned", rows.hasNext());
+        excerpt = rows.nextRow().getValue("rep:excerpt(.)");
+        assertNotNull("No excerpt created", excerpt);
+        assertTrue("Title must not be present in excerpt",
+                excerpt.getString().indexOf("Apache") == -1);
+    }
+
+    public void testExcerptOnExcludedProperty() throws RepositoryException {
+        Node node = testRootNode.addNode(nodeName1, NT_UNSTRUCTURED);
+        node.setProperty("rule", "excerpt");
+        node.setProperty("title", TEXT);
+        testRootNode.save();
+        String stmt = "/jcr:root" + testRootNode.getPath() +
+                "/*[jcr:contains(., 'quick')]/rep:excerpt(.)";
+        QueryResult result = executeQuery(stmt);
+        checkResult(result, new Node[]{node});
+        Value excerpt = result.getRows().nextRow().getValue("rep:excerpt(.)");
+        assertNotNull("No excerpt created", excerpt);
+    }
+
+    public void testUseInExcerptWithAggregate() throws RepositoryException {
+        Node node = testRootNode.addNode(nodeName1, NT_UNSTRUCTURED);
+        node.setProperty("rule", "excerpt");
+        node.setProperty("title", "Apache Jackrabbit");
+        node.setProperty("text", "Jackrabbit is a JCR implementation");
+        Node aggregated = node.addNode("aggregated-node", NT_UNSTRUCTURED);
+        aggregated.setProperty("rule", "excerpt");
+        aggregated.setProperty("title", "Apache Jackrabbit");
+        aggregated.setProperty("text", "Jackrabbit is a JCR implementation");
+        testRootNode.save();
+
+        String stmt = "/jcr:root" + testRootNode.getPath() +
+                "/*[jcr:contains(., 'jackrabbit')]/rep:excerpt(.)";
+        RowIterator rows = executeQuery(stmt).getRows();
+        assertTrue("No results returned", rows.hasNext());
+        Value excerpt;
+        while (rows.hasNext()) {
+            excerpt = rows.nextRow().getValue("rep:excerpt(.)");
+            assertNotNull("No excerpt created", excerpt);
+            assertTrue("Title must not be present in excerpt",
+                    excerpt.getString().indexOf("Apache") == -1);
+            int idx = 0;
+            int numHighlights = 0;
+            for (;;) {
+                idx = excerpt.getString().indexOf("<strong>", idx);
+                if (idx == -1) {
+                    break;
+                }
+                numHighlights++;
+                int endIdx = excerpt.getString().indexOf("</strong>", idx);
+                assertEquals("wrong highlight", "Jackrabbit",
+                        excerpt.getString().substring(idx + "<strong>".length(), endIdx));
+                idx = endIdx;
+            }
+            assertTrue("Missing highlight", numHighlights > 0);
+        }
+
+        stmt = "/jcr:root" + testRootNode.getPath() +
+                "/*[jcr:contains(., 'apache')]/rep:excerpt(.)";
+        rows = executeQuery(stmt).getRows();
+        assertTrue("No results returned", rows.hasNext());
+        excerpt = rows.nextRow().getValue("rep:excerpt(.)");
+        assertNotNull("No excerpt created", excerpt);
+        assertTrue("Title must not be present in excerpt",
+                excerpt.getString().indexOf("Apache") == -1);
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/LargeResultSetTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/LargeResultSetTest.java
new file mode 100644
index 0000000..b980dd0
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/LargeResultSetTest.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.query.lucene;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryResult;
+import javax.jcr.query.RowIterator;
+
+import org.apache.jackrabbit.core.query.AbstractIndexingTest;
+
+/**
+ * <code>LargeResultSetTest</code>...
+ * 
+ * TODO what does this test actually do?
+ */
+public class LargeResultSetTest extends AbstractIndexingTest {
+
+    public void testResultSet() throws RepositoryException {
+        int count = createNodes(testRootNode, 10, 5, 0);
+        session.save();
+
+        SearchIndex index = getSearchIndex();
+        int resultFetchSize = index.getResultFetchSize();
+        try {
+            String stmt = testPath + "//*[@" + jcrPrimaryType + "] order by @jcr:score descending";
+
+            // with result fetch size Integer.MAX_VALUE
+            readResult(executeQuery(stmt), count);
+
+            // with result fetch size 100
+            index.setResultFetchSize(100);
+            readResult(executeQuery(stmt), count);
+
+            // with 100 limit
+            Query query = qm.createQuery(stmt, Query.XPATH);
+            query.setLimit(100);
+            readResult(query.execute(), 100);
+        } finally {
+            index.setResultFetchSize(resultFetchSize);
+        }
+    }
+
+    protected void tearDown() throws Exception {
+        int count = 0;
+        for (NodeIterator it = testRootNode.getNodes(); it.hasNext();) {
+            it.nextNode().remove();
+            count++;
+            if (count % 10000 == 0) {
+                session.save();
+            }
+        }
+        session.save();
+        super.tearDown();
+    }
+
+    /*
+     * use default ws
+     */
+    protected String getWorkspaceName() {
+        return null;
+    }
+
+    private void readResult(QueryResult result, int count) throws RepositoryException {
+        for (RowIterator rows = result.getRows(); rows.hasNext(); ) {
+            rows.nextRow();
+            count--;
+        }
+        assertEquals(0, count);
+    }
+
+    private int createNodes(Node n, int nodesPerLevel, int levels, int count)
+            throws RepositoryException {
+        levels--;
+        for (int i = 0; i < nodesPerLevel; i++) {
+            Node child = n.addNode("node" + i);
+            count++;
+            if (count % 10000 == 0) {
+                session.save();
+            }
+            if (levels > 0) {
+                count = createNodes(child, nodesPerLevel, levels, count);
+            }
+        }
+        return count;
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/LazyTextExtractorFieldTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/LazyTextExtractorFieldTest.java
new file mode 100644
index 0000000..4904f77
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/LazyTextExtractorFieldTest.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.query.lucene;
+
+import org.apache.jackrabbit.core.data.RandomInputStream;
+import org.apache.jackrabbit.core.query.AbstractIndexingTest;
+import org.apache.jackrabbit.core.query.lucene.LazyTextExtractorField.ParsingTask;
+import org.apache.jackrabbit.core.value.InternalValue;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.parser.Parser;
+
+public class LazyTextExtractorFieldTest extends AbstractIndexingTest {
+
+    /**
+     * @see <a
+     *      href="https://issues.apache.org/jira/browse/JCR-3296">JCR-3296</a>
+     *      Indexing ignored file types creates some garbage
+     */
+    public void testEmptyParser() throws Exception {
+
+        InternalValue val = InternalValue
+                .create(new RandomInputStream(1, 1024));
+
+        Metadata metadata = new Metadata();
+        metadata.set(Metadata.CONTENT_TYPE, "application/java-archive");
+        metadata.set(Metadata.CONTENT_ENCODING, "UTF-8");
+
+        Parser p = getSearchIndex().getParser();
+
+        ParsingTask task = new ParsingTask(p, val, metadata, Integer.MAX_VALUE) {
+            public void setExtractedText(String value) {
+                assertEquals("", value);
+            }
+        };
+        task.run();
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SQL2IndexingAggregateTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SQL2IndexingAggregateTest.java
index 24f42c0..ab00e7e 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SQL2IndexingAggregateTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SQL2IndexingAggregateTest.java
@@ -353,4 +353,18 @@ public class SQL2IndexingAggregateTest extends AbstractIndexingTest {
                 + "]) and type = 'testnode' ";
         checkResult(qm.createQuery(sql, JCR_SQL2).execute(), 1);
     }
+
+    public void testAggregatedProperty() throws Exception {
+
+        Node parent = testRootNode.addNode("parent",
+                JcrConstants.NT_UNSTRUCTURED);
+        Node child = parent.addNode("child", JcrConstants.NT_UNSTRUCTURED);
+        child.setProperty("property",
+                "the quick brown fox jumps over the lazy dog.");
+        testRootNode.getSession().save();
+
+        String sql = "SELECT * FROM [nt:unstructured] as u WHERE CONTAINS (u.*, 'dog') ";
+        executeSQL2Query(sql, new Node[] { parent, child });
+    }
+
 }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SearchIndexConsistencyCheckTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SearchIndexConsistencyCheckTest.java
new file mode 100644
index 0000000..d8aa2bb
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SearchIndexConsistencyCheckTest.java
@@ -0,0 +1,327 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.query.lucene;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.core.SearchManager;
+import org.apache.jackrabbit.core.TestHelper;
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.query.lucene.hits.AbstractHitCollector;
+import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TermQuery;
+
+public class SearchIndexConsistencyCheckTest extends AbstractJCRTest {
+
+    private boolean keepRunning;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public void testIndexMissesNode() throws Exception {
+
+        Session s = getHelper().getSuperuserSession();
+        SearchManager searchManager = TestHelper.getSearchManager(s);
+        SearchIndex searchIndex = (SearchIndex) searchManager.getQueryHandler();
+
+        Node foo = testRootNode.addNode("foo");
+        testRootNode.getSession().save();
+        NodeId fooId = new NodeId(foo.getIdentifier());
+
+        Iterator<NodeId> remove = Collections.singletonList(fooId).iterator();
+        Iterator<NodeState> add = Collections.<NodeState>emptyList().iterator();
+
+        searchIndex.updateNodes(remove, add);
+
+        ConsistencyCheck consistencyCheck = searchIndex.runConsistencyCheck();
+        List<ConsistencyCheckError> errors = consistencyCheck.getErrors();
+        assertEquals("Expected 1 index consistency error", 1, errors.size());
+
+        ConsistencyCheckError error = errors.iterator().next();
+        assertEquals("Different node was reported to be missing", error.id, fooId);
+
+        consistencyCheck.repair(false);
+
+        assertTrue("Index was not repaired properly", searchIndexContainsNode(searchIndex, fooId));
+
+        assertTrue("Consistency check still reports errors", searchIndex.runConsistencyCheck().getErrors().isEmpty());
+    }
+
+    public void testMissingNodeDoubleCheck() throws Exception {
+        Session s = getHelper().getSuperuserSession();
+        SearchManager searchManager = TestHelper.getSearchManager(s);
+        SearchIndex searchIndex = (SearchIndex) searchManager.getQueryHandler();
+
+        Node foo = testRootNode.addNode("foo");
+        testRootNode.getSession().save();
+        NodeId fooId = new NodeId(foo.getIdentifier());
+
+        Iterator<NodeId> remove = Collections.singletonList(fooId).iterator();
+        Iterator<NodeState> add = Collections.<NodeState>emptyList().iterator();
+
+        searchIndex.updateNodes(remove, add);
+
+        ConsistencyCheck consistencyCheck = searchIndex.runConsistencyCheck();
+        List<ConsistencyCheckError> errors = consistencyCheck.getErrors();
+        assertEquals("Expected 1 index consistency error", 1, errors.size());
+
+        // now add foo to the index again so that double check finds a false positive
+        remove = Collections.<NodeId>emptyList().iterator();
+        add = Collections.singletonList(new NodeState(fooId, null, null, 1, false)).iterator();
+
+        searchIndex.updateNodes(remove, add);
+
+        consistencyCheck.doubleCheckErrors();
+
+        assertTrue("Consistency double check of missing node failed", consistencyCheck.getErrors().isEmpty());
+
+    }
+
+    public void testIndexContainsUnknownNode() throws Exception {
+
+        Session s = getHelper().getSuperuserSession();
+        SearchManager searchManager = TestHelper.getSearchManager(s);
+        SearchIndex searchIndex = (SearchIndex) searchManager.getQueryHandler();
+
+        NodeId nodeId = new NodeId(0, 0);
+        NodeState nodeState = new NodeState(nodeId, null, null, 1, false);
+
+        Iterator<NodeId> remove = Collections.<NodeId>emptyList().iterator();
+        Iterator<NodeState> add = Collections.singletonList(nodeState).iterator();
+
+        searchIndex.updateNodes(remove, add);
+
+        ConsistencyCheck consistencyCheck = searchIndex.runConsistencyCheck();
+        List<ConsistencyCheckError> errors = consistencyCheck.getErrors();
+        assertEquals("Expected 1 index consistency error", 1, errors.size());
+
+        ConsistencyCheckError error = errors.iterator().next();
+        assertEquals("Different node was reported to be unknown", error.id, nodeId);
+
+        consistencyCheck.repair(false);
+
+        assertFalse("Index was not repaired properly", searchIndexContainsNode(searchIndex, nodeId));
+
+        assertTrue("Consistency check still reports errors", searchIndex.runConsistencyCheck().getErrors().isEmpty());
+    }
+
+    public void testUnknownNodeDoubleCheck() throws Exception {
+
+        Session s = getHelper().getSuperuserSession();
+        SearchManager searchManager = TestHelper.getSearchManager(s);
+        SearchIndex searchIndex = (SearchIndex) searchManager.getQueryHandler();
+
+        NodeId nodeId = new NodeId(0, 0);
+        NodeState nodeState = new NodeState(nodeId, null, null, 1, false);
+
+        Iterator<NodeId> remove = Collections.<NodeId>emptyList().iterator();
+        Iterator<NodeState> add = Collections.singletonList(nodeState).iterator();
+
+        searchIndex.updateNodes(remove, add);
+
+        ConsistencyCheck consistencyCheck = searchIndex.runConsistencyCheck();
+        List<ConsistencyCheckError> errors = consistencyCheck.getErrors();
+        assertEquals("Expected 1 index consistency error", 1, errors.size());
+
+        // now remove the unknown node from the index again so that double check finds a false positive
+        remove = Collections.singletonList(nodeId).iterator();
+        add = Collections.<NodeState>emptyList().iterator();
+
+        searchIndex.updateNodes(remove, add);
+
+        consistencyCheck.doubleCheckErrors();
+
+        assertTrue("Consistency double check of deleted node failed", consistencyCheck.getErrors().isEmpty());
+    }
+
+    public void testIndexMissesAncestor() throws Exception {
+        Session s = getHelper().getSuperuserSession();
+        SearchManager searchManager = TestHelper.getSearchManager(s);
+        SearchIndex searchIndex = (SearchIndex) searchManager.getQueryHandler();
+
+        Node foo = testRootNode.addNode("foo");
+        Node bar = foo.addNode("bar");
+        testRootNode.getSession().save();
+        NodeId fooId = new NodeId(foo.getIdentifier());
+        NodeId barId = new NodeId(bar.getIdentifier());
+
+        Iterator<NodeId> remove = Collections.singletonList(fooId).iterator();
+        Iterator<NodeState> add = Collections.<NodeState>emptyList().iterator();
+
+        searchIndex.updateNodes(remove, add);
+
+        ConsistencyCheck consistencyCheck = searchIndex.runConsistencyCheck();
+        List<ConsistencyCheckError> errors = consistencyCheck.getErrors();
+
+        assertEquals("Expected 2 index consistency errors", 2, errors.size());
+
+        assertEquals("Different node was reported to have missing parent", errors.get(0).id, barId);
+        assertEquals("Different node was reported to be missing", errors.get(1).id, fooId);
+
+        consistencyCheck.repair(false);
+
+        assertTrue("Index was not repaired properly", searchIndexContainsNode(searchIndex, fooId));
+
+        assertTrue("Consistency check still reports errors", searchIndex.runConsistencyCheck().getErrors().isEmpty());
+    }
+
+    public void testMissingAncestorDoubleCheck() throws Exception {
+
+        Session s = getHelper().getSuperuserSession();
+        SearchManager searchManager = TestHelper.getSearchManager(s);
+        SearchIndex searchIndex = (SearchIndex) searchManager.getQueryHandler();
+
+        Node foo = testRootNode.addNode("foo");
+        foo.addNode("bar");
+        testRootNode.getSession().save();
+        NodeId fooId = new NodeId(foo.getIdentifier());
+
+        Iterator<NodeId> remove = Collections.singletonList(fooId).iterator();
+        Iterator<NodeState> add = Collections.<NodeState>emptyList().iterator();
+
+        searchIndex.updateNodes(remove, add);
+
+        ConsistencyCheck consistencyCheck = searchIndex.runConsistencyCheck();
+        List<ConsistencyCheckError> errors = consistencyCheck.getErrors();
+
+        assertEquals("Expected 2 index consistency errors", 2, errors.size());
+
+        remove = Collections.<NodeId>emptyList().iterator();
+        add = Collections.singletonList(new NodeState(fooId, null, null, 1, true)).iterator();
+
+        searchIndex.updateNodes(remove, add);
+
+        consistencyCheck.doubleCheckErrors();
+
+        assertTrue("Consistency double check of missing ancestor failed", consistencyCheck.getErrors().isEmpty());
+    }
+
+    public void testIndexContainsMultipleEntries() throws Exception {
+        Session s = getHelper().getSuperuserSession();
+        SearchManager searchManager = TestHelper.getSearchManager(s);
+        SearchIndex searchIndex = (SearchIndex) searchManager.getQueryHandler();
+
+        Node foo = testRootNode.addNode("foo");
+        testRootNode.getSession().save();
+        NodeId fooId = new NodeId(foo.getIdentifier());
+
+        NodeState nodeState = new NodeState(fooId, null, null, 1, false);
+        Iterator<NodeId> remove = Collections.<NodeId>emptyList().iterator();
+        Iterator<NodeState> add = Arrays.asList(nodeState).iterator();
+
+        searchIndex.updateNodes(remove, add);
+
+        searchIndex.flush();
+
+        remove = Collections.<NodeId>emptyList().iterator();
+        add = Arrays.asList(nodeState).iterator();
+
+        searchIndex.updateNodes(remove, add);
+
+        ConsistencyCheck consistencyCheck = searchIndex.runConsistencyCheck();
+        List<ConsistencyCheckError> errors = consistencyCheck.getErrors();
+
+        assertEquals("Expected 1 index consistency error", 1, errors.size());
+        assertEquals("Different node was reported to be duplicate", errors.get(0).id, fooId);
+
+        consistencyCheck.repair(false);
+
+        assertTrue("Index was not repaired properly", searchIndexContainsNode(searchIndex, fooId));
+        assertTrue("Consistency check still reports errors", searchIndex.runConsistencyCheck().getErrors().isEmpty());
+    }
+
+    /**
+     * Stress test on the double check mechanism
+     */
+    public void testDoubleCheckStressTest() throws Exception {
+        Thread t = new Thread(new Runnable() {
+            private Session s = getHelper().getReadWriteSession();
+            @Override
+            public void run() {
+                while (keepRunning) {
+                    try {
+                        Node foo = s.getRootNode().getNode(testPath).addNode("foo");
+                        s.save();
+                        foo.remove();
+                        s.save();
+                    } catch (RepositoryException e) {
+                        System.out.println(e);
+                    }
+                }
+            }
+        });
+
+        Session s = getHelper().getSuperuserSession();
+        SearchManager searchManager = TestHelper.getSearchManager(s);
+        SearchIndex searchIndex = (SearchIndex) searchManager.getQueryHandler();
+
+        keepRunning = true;
+        try {
+            t.start();
+            Thread.sleep(100);
+            for (int i = 100; i > 0; i--) {
+                final ConsistencyCheck consistencyCheck = searchIndex.runConsistencyCheck();
+                consistencyCheck.doubleCheckErrors();
+                final List<ConsistencyCheckError> errors = consistencyCheck.getErrors();
+                assertTrue(errors.isEmpty());
+            }
+        } finally {
+            keepRunning = false;
+        }
+
+    }
+
+    private boolean searchIndexContainsNode(SearchIndex searchIndex, NodeId nodeId) throws IOException {
+        final List<Integer> docs = new ArrayList<Integer>(1);
+        final IndexReader reader = searchIndex.getIndexReader();
+        try {
+            IndexSearcher searcher = new IndexSearcher(reader);
+            try {
+                Query q = new TermQuery(new Term(FieldNames.UUID, nodeId.toString()));
+                searcher.search(q, new AbstractHitCollector() {
+                    @Override
+                    protected void collect(final int doc, final float score) {
+                        docs.add(doc);
+                    }
+                });
+            } finally {
+                searcher.close();
+            }
+        } finally {
+            Util.closeOrRelease(reader);
+        }
+        return !docs.isEmpty();
+
+    }
+
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SearchIndexTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SearchIndexTest.java
new file mode 100644
index 0000000..68ad0de
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SearchIndexTest.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.query.lucene;
+
+import junit.framework.TestCase;
+
+public class SearchIndexTest extends TestCase {
+
+    /**
+     * @see <a href="https://issues.apache.org/jira/browse/JCR-3236">JCR-3236</a>
+     */
+    public void testSetAnalyzer() {
+        String[] analyzers = {
+                "org.apache.lucene.analysis.SimpleAnalyzer",
+                "org.apache.lucene.analysis.StopAnalyzer",
+                "org.apache.lucene.analysis.standard.StandardAnalyzer" };
+        SearchIndex index = new SearchIndex();
+        for (String analyzer : analyzers) {
+            index.setAnalyzer(analyzer);
+            assertEquals(analyzer, index.getAnalyzer());
+        }
+    }
+
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SynonymProviderTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SynonymProviderTest.java
new file mode 100644
index 0000000..cb4feb3
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/SynonymProviderTest.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.query.lucene;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Node;
+
+import org.apache.jackrabbit.core.query.AbstractQueryTest;
+
+/**
+ * <code>SynonymProviderTest</code> contains test cases for the
+ * <code>PropertiesSynonymProvider</code> class.
+ * This test assumes that the following synonyms are defined:
+ * <ul>
+ * <li>quick <-> fast</li>
+ * <li>sluggish <-> lazy</li>
+ * <li>ASF <-> Apache Software Foundation</li>
+ * </ul>
+ */
+public class SynonymProviderTest extends AbstractQueryTest {
+
+    public void testSynonyms() throws RepositoryException {
+        Node n = testRootNode.addNode(nodeName1);
+        n.setProperty(propertyName1, "The quick brown fox jumps over the lazy dog.");
+        testRootNode.save();
+        executeXPathQuery(testPath + "//*[jcr:contains(., '~fast')]", new Node[]{n});
+        executeXPathQuery(testPath + "//*[jcr:contains(., '~Fast')]", new Node[]{n});
+        executeXPathQuery(testPath + "//*[jcr:contains(., '~quick')]", new Node[]{n});
+        executeXPathQuery(testPath + "//*[jcr:contains(., '~sluggish')]", new Node[]{n});
+        executeXPathQuery(testPath + "//*[jcr:contains(., '~sluGGish')]", new Node[]{n});
+        executeXPathQuery(testPath + "//*[jcr:contains(., '~lazy')]", new Node[]{n});
+        // check term which is not in the synonym provider
+        executeXPathQuery(testPath + "//*[jcr:contains(., '~brown')]", new Node[]{n});
+    }
+
+    public void testPhrase() throws RepositoryException {
+        Node n = testRootNode.addNode(nodeName1);
+        n.setProperty(propertyName1, "Licensed to the Apache Software Foundation ...");
+        testRootNode.save();
+        executeXPathQuery(testPath + "//*[jcr:contains(., '~ASF')]", new Node[]{n});
+        executeXPathQuery(testPath + "//*[jcr:contains(., '~asf')]", new Node[]{n});
+        executeXPathQuery(testPath + "//*[jcr:contains(., 'asf')]", new Node[]{});
+    }
+
+    public void disabled_testReload() throws RepositoryException, InterruptedException {
+        for (int i = 0; i < 60; i++) {
+            Thread.sleep(1 * 1000);
+            executeXPathQuery(testPath + "//*[jcr:contains(., '~asf')]", new Node[]{});
+        }
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/TestAll.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/TestAll.java
index c00cee2..fffd285 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/TestAll.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/TestAll.java
@@ -16,11 +16,13 @@
  */
 package org.apache.jackrabbit.core.query.lucene;
 
-import org.apache.jackrabbit.test.ConcurrentTestSuite;
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
 
+import org.apache.jackrabbit.core.query.lucene.hits.ArrayHitsTest;
+import org.apache.jackrabbit.test.ConcurrentTestSuite;
+
 /**
  * Test suite that includes all testcases for the Search module.
  */
@@ -44,6 +46,13 @@ public class TestAll extends TestCase {
         suite.addTestSuite(IndexingConfigurationImplTest.class);
         suite.addTestSuite(SQL2IndexingAggregateTest.class);
         suite.addTestSuite(SQL2IndexingAggregateTest2.class);
+        suite.addTestSuite(LazyTextExtractorFieldTest.class);
+        suite.addTestSuite(IndexInfosTest.class);
+        suite.addTestSuite(IndexingRuleTest.class);
+        suite.addTestSuite(TextExtractionQueryTest.class);
+        suite.addTestSuite(ArrayHitsTest.class);
+        suite.addTestSuite(IndexFormatVersionTest.class);
+        suite.addTestSuite(SynonymProviderTest.class);
 
         return suite;
     }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/TextExtractionQueryTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/TextExtractionQueryTest.java
new file mode 100644
index 0000000..692d783
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/TextExtractionQueryTest.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.query.lucene;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.util.Calendar;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.query.InvalidQueryException;
+import javax.jcr.query.Query;
+
+import org.apache.jackrabbit.core.query.AbstractIndexingTest;
+import org.apache.jackrabbit.core.query.FulltextQueryTest;
+
+public class TextExtractionQueryTest extends AbstractIndexingTest {
+
+    public void testFileContains() throws Exception {
+        assertFileContains("test.txt", "text/plain",
+                "AE502DBEA2C411DEBD340AD156D89593");
+        assertFileContains("test.rtf", "application/rtf", "quick brown fox");
+    }
+
+    public void testNtFile() throws RepositoryException, IOException {
+        Node file = testRootNode.addNode(nodeName1, "nt:file");
+        Node resource = file.addNode("jcr:content", "nt:resource");
+        resource.setProperty("jcr:encoding", "UTF-8");
+        resource.setProperty("jcr:mimeType", "text/plain");
+        ByteArrayOutputStream data = new ByteArrayOutputStream();
+        OutputStreamWriter writer = new OutputStreamWriter(data, "UTF-8");
+        writer.write("The quick brown fox jumps over the lazy dog.");
+        writer.close();
+        resource.setProperty("jcr:data", new ByteArrayInputStream(data.toByteArray()));
+        resource.setProperty("jcr:lastModified", Calendar.getInstance());
+
+        testRootNode.save();
+        String xpath = testPath + "/*[jcr:contains(jcr:content, 'lazy')]";
+        executeXPathQuery(xpath, new Node[]{file});
+    }
+
+    private void assertFileContains(String name, String type,
+            String... statements) throws Exception {
+        while (testRootNode.hasNode(nodeName1)) {
+            testRootNode.getNode(nodeName1).remove();
+        }
+        Node resource = testRootNode.addNode(nodeName1, NodeType.NT_RESOURCE);
+        resource.setProperty("jcr:mimeType", type);
+        InputStream stream = FulltextQueryTest.class.getResourceAsStream(name);
+        try {
+            resource.setProperty("jcr:data", stream);
+        } finally {
+            stream.close();
+        }
+        testRootNode.save();
+        flushSearchIndex();
+        for (String statement : statements) {
+            assertContainsQuery(statement, true);
+        }
+    }
+
+    private void assertContainsQuery(String statement, boolean match)
+            throws InvalidQueryException, RepositoryException {
+        StringBuffer stmt = new StringBuffer();
+        stmt.append("/jcr:root").append(testRoot).append("/*");
+        stmt.append("[jcr:contains(., '").append(statement);
+        stmt.append("')]");
+
+        Query q = qm.createQuery(stmt.toString(), Query.XPATH);
+        checkResult(q.execute(), match ? 1 : 0);
+
+        stmt = new StringBuffer();
+        stmt.append("SELECT * FROM nt:base ");
+        stmt.append("WHERE jcr:path LIKE '").append(testRoot).append("/%' ");
+        stmt.append("AND CONTAINS(., '").append(statement).append("')");
+
+        q = qm.createQuery(stmt.toString(), Query.SQL);
+        checkResult(q.execute(), match ? 1 : 0);
+    }
+
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/UtilTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/UtilTest.java
new file mode 100644
index 0000000..f3a2e49
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/query/lucene/UtilTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.query.lucene;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+import junit.framework.TestCase;
+import org.apache.jackrabbit.value.ValueFactoryImpl;
+import org.junit.Test;
+
+public class UtilTest extends TestCase {
+
+    public void testComparableContract(){
+        //The test data needs to be greater than 32 in length to trigger TimSort
+        //The data was obtained by running multiple runs with random sequence
+        //and then reverse engineered from it :)
+        Integer[] data = {null,25,21,5,null,23,10,19,10,null,null,10,24,null,10,null,7,11,
+                null,7,null,14,26,0,6,19,null,5,null,4,28,19,5,28,18,14,12,16,14,15};
+        List<Value[]> testData = createValueArrayList(data);
+        Collections.sort(testData, new ValueArrayComparator());
+    }
+
+    private static List<Value[]> createValueArrayList(Integer[] data){
+        List<Value[]> result = new ArrayList<Value[]>(data.length);
+        for(Integer i : data){
+            Value[] r = null;
+            if(i != null){
+                r = new Value[]{ValueFactoryImpl.getInstance().createValue(i.longValue())};
+            }
+            result.add(r);
+        }
+        return result;
+    }
+
+    private static class ValueArrayComparator implements Comparator<Value[]> {
+        @Override
+        public int compare(Value[] a, Value[] b) {
+            try {
+                return Util.compare(a, b);
+            } catch (RepositoryException e) {
+                throw new RuntimeException("Unable to compare values "
+                        + Arrays.toString(a) + " and " + Arrays.toString(b), e);
+            }
+        }
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/SimpleCredentialsAuthenticationTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/SimpleCredentialsAuthenticationTest.java
index f5ef7f2..7917a0f 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/SimpleCredentialsAuthenticationTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/SimpleCredentialsAuthenticationTest.java
@@ -123,6 +123,10 @@ public class SimpleCredentialsAuthenticationTest extends AbstractJCRTest {
             return false;
         }
 
+        public boolean isSystemUser() {
+            return false;
+        }
+
         public Credentials getCredentials() throws RepositoryException {
             return creds;
         }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/CompatTokenProviderTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/CompatTokenProviderTest.java
new file mode 100644
index 0000000..1528b61
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/CompatTokenProviderTest.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.security.authentication.token;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.jackrabbit.test.NotExecutableException;
+
+public class CompatTokenProviderTest extends AbstractJCRTest {
+
+    private User testuser;
+    private String userId;
+
+    private SessionImpl session;
+    private CompatTokenProvider tokenProvider;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        if (superuser instanceof SessionImpl) {
+            UserManager umgr = ((SessionImpl) superuser).getUserManager();
+            if (!umgr.isAutoSave()) {
+                umgr.autoSave(true);
+            }
+            String uid = "test";
+            while (umgr.getAuthorizable(uid) != null) {
+                uid += "_";
+            }
+
+            testuser = umgr.createUser(uid, uid);
+            userId = testuser.getID();
+        } else {
+            throw new NotExecutableException();
+        }
+
+        if (superuser.nodeExists(((ItemBasedPrincipal) testuser.getPrincipal()).getPath())) {
+            session = (SessionImpl) superuser;
+        } else {
+            session = (SessionImpl) getHelper().getSuperuserSession("security");
+        }
+        tokenProvider = new CompatTokenProvider((SessionImpl) session, TokenBasedAuthentication.TOKEN_EXPIRATION);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        try {
+            testuser.remove();
+            session.logout();
+        } finally {
+            super.tearDown();
+        }
+    }
+
+    public void testCreateTokenFromCredentials() throws Exception {
+        TokenInfo info = tokenProvider.createToken(testuser, new SimpleCredentials(userId, new char[0]));
+        assertTokenInfo(info);
+    }
+
+    public void testCreateTokenIsCaseInsensitive() throws Exception {
+        String upperCaseUserId = userId.toUpperCase();
+        TokenInfo info = tokenProvider.createToken(testuser, new SimpleCredentials(upperCaseUserId, new char[0]));
+        assertTokenInfo(info);
+    }
+
+    public void testTokenNode() throws Exception {
+        Map<String, String> privateAttributes = new HashMap<String, String>();
+        privateAttributes.put(".token_exp", "value");
+        privateAttributes.put(".tokenTest", "value");
+        privateAttributes.put(".token_something", "value");
+
+        Map<String, String> publicAttributes = new HashMap<String, String>();
+        publicAttributes.put("any", "value");
+        publicAttributes.put("another", "value");
+
+        Map<String, String> attributes = new HashMap<String, String>();
+        attributes.putAll(publicAttributes);
+        attributes.putAll(privateAttributes);
+
+        SimpleCredentials sc = new SimpleCredentials(userId, userId.toCharArray());
+        for (String s : attributes.keySet()) {
+            sc.setAttribute(s, attributes.get(s));
+        }
+
+        TokenInfo info = tokenProvider.createToken(testuser, sc);
+        Node tokenNode = getTokenNode(info);
+        Property prop = tokenNode.getProperty(".token.key");
+        assertNotNull(prop);
+        assertEquals(PropertyType.STRING, prop.getType());
+        assertFalse(prop.getDefinition().isProtected());
+
+        prop = tokenNode.getProperty(".token.exp");
+        assertNotNull(prop);
+        assertEquals(PropertyType.DATE, prop.getType());
+        assertFalse(prop.getDefinition().isProtected());
+
+        for (String key : privateAttributes.keySet()) {
+            assertEquals(privateAttributes.get(key), tokenNode.getProperty(key).getString());
+        }
+
+        for (String key : publicAttributes.keySet()) {
+            assertEquals(publicAttributes.get(key), tokenNode.getProperty(key).getString());
+        }
+    }
+
+    public void testGetTokenInfoFromInvalidToken() throws Exception {
+        List<String> invalid = new ArrayList<String>();
+        invalid.add("/invalid");
+        invalid.add(UUID.randomUUID().toString());
+
+        try {
+            for (String token : invalid) {
+                TokenInfo info = tokenProvider.getTokenInfo(token);
+                assertNull(info);
+            }
+        } catch (Exception e) {
+            // success
+        }
+    }
+
+    public void testGetTokenInfo() throws Exception {
+        String token = tokenProvider.createToken(testuser, new SimpleCredentials(userId, userId.toCharArray())).getToken();
+        TokenInfo info = tokenProvider.getTokenInfo(token);
+        assertTokenInfo(info);
+    }
+
+    public void testIsExpired() throws Exception {
+        TokenInfo info = tokenProvider.createToken(testuser, new SimpleCredentials(userId, userId.toCharArray()));
+
+        long loginTime = waitForSystemTimeIncrement(System.currentTimeMillis());
+        assertFalse(info.isExpired(loginTime));
+        assertTrue(info.isExpired(loginTime + TokenBasedAuthentication.TOKEN_EXPIRATION));
+    }
+
+    public void testReset() throws Exception {
+        TokenInfo info = tokenProvider.createToken(testuser, new SimpleCredentials(userId, userId.toCharArray()));
+        long expTime = getTokenNode(info).getProperty(".token.exp").getLong();
+
+        long loginTime = System.currentTimeMillis();
+        assertFalse(info.resetExpiration(loginTime));
+        assertTrue(info.resetExpiration(loginTime + TokenBasedAuthentication.TOKEN_EXPIRATION / 2));
+        long expTime2 = getTokenNode(info).getProperty(".token.exp").getLong();
+        assertFalse(expTime == expTime2);
+    }
+
+    //--------------------------------------------------------------------------
+    private static void assertTokenInfo(TokenInfo info) {
+        assertNotNull(info);
+        assertNotNull(info.getToken());
+        assertFalse(info.isExpired(new Date().getTime()));
+    }
+
+    private Node getTokenNode(TokenInfo info) throws RepositoryException {
+        return CompatTokenProvider.getTokenNode(info.getToken(), session);
+    }
+
+    private static long waitForSystemTimeIncrement(long old){
+        while (old == System.currentTimeMillis()) {
+            // wait for system timer to move
+        }
+        return System.currentTimeMillis();
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TestAll.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TestAll.java
index b7c23cb..6703996 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TestAll.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TestAll.java
@@ -32,8 +32,11 @@ public class TestAll extends TestCase {
     public static Test suite() {
         TestSuite suite = new TestSuite("org.apache.jackrabbit.core.security.authentication.token tests");
 
+        suite.addTestSuite(TokenBasedAuthenticationCompatTest.class);
         suite.addTestSuite(TokenBasedAuthenticationTest.class);
         suite.addTestSuite(TokenBasedLoginTest.class);
+        suite.addTestSuite(TokenProviderTest.class);
+        suite.addTestSuite(CompatTokenProviderTest.class);
 
         return suite;
     }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationCompatTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationCompatTest.java
new file mode 100644
index 0000000..e6242aa
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationCompatTest.java
@@ -0,0 +1,232 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.security.authentication.token;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.UUID;
+import javax.jcr.Credentials;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.version.VersionException;
+
+import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+
+/**
+ * <code>TokenBasedAuthenticationCompatTest</code>...
+ */
+public class TokenBasedAuthenticationCompatTest extends AbstractJCRTest {
+
+    SessionImpl adminSession;
+    User testUser;
+
+    Node tokenNode;
+    String token;
+
+    TokenBasedAuthentication nullTokenAuth;
+    TokenBasedAuthentication validTokenAuth;
+
+    TokenCredentials tokenCreds;
+    Credentials simpleCreds = new SimpleCredentials("uid", "pw".toCharArray());
+    Credentials creds = new Credentials() {};
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        System.setProperty(TokenBasedAuthentication.PARAM_COMPAT, Boolean.TRUE.toString());
+
+        adminSession = (SessionImpl) getHelper().getSuperuserSession("security");
+        testUser = adminSession.getUserManager().createUser(UUID.randomUUID().toString(), "pw");
+        adminSession.save();
+
+        SimpleCredentials sc = new SimpleCredentials(testUser.getID(), "pw".toCharArray());
+        sc.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, "");
+
+        CompatTokenProvider tp = new CompatTokenProvider(adminSession, TokenBasedAuthentication.TOKEN_EXPIRATION);
+        TokenInfo ti = tp.createToken(testUser, sc);
+        tokenNode = CompatTokenProvider.getTokenNode(ti.getToken(), adminSession);
+
+        token = ti.getToken();
+
+        nullTokenAuth = new TokenBasedAuthentication(null, -1, adminSession);
+        validTokenAuth = new TokenBasedAuthentication(token, 7200, adminSession);
+
+        tokenCreds = new TokenCredentials(token);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        System.setProperty(TokenBasedAuthentication.PARAM_COMPAT, Boolean.FALSE.toString());
+        super.tearDown();
+    }
+
+    private TokenBasedAuthentication expiredToken() throws RepositoryException, LockException, ConstraintViolationException, VersionException {
+        Calendar cal = Calendar.getInstance();
+        cal.setTimeInMillis(new Date().getTime() - 100);
+        tokenNode.setProperty(".token.exp", cal);
+        adminSession.save();
+        return new TokenBasedAuthentication(token, TokenBasedAuthentication.TOKEN_EXPIRATION, adminSession);
+    }
+
+    public void testCanHandle() throws RepositoryException {
+        assertTrue(validTokenAuth.canHandle(tokenCreds));
+        assertFalse(nullTokenAuth.canHandle(tokenCreds));
+
+        assertFalse(validTokenAuth.canHandle(simpleCreds));
+        assertFalse(nullTokenAuth.canHandle(simpleCreds));
+
+        assertFalse(validTokenAuth.canHandle(creds));
+        assertFalse(nullTokenAuth.canHandle(creds));
+
+        TokenBasedAuthentication expiredToken = expiredToken();
+        assertTrue(expiredToken.canHandle(tokenCreds));
+    }
+
+    public void testExpiry() throws RepositoryException {
+        assertTrue(validTokenAuth.authenticate(tokenCreds));
+
+        TokenBasedAuthentication expiredToken = expiredToken();
+        assertFalse(expiredToken.authenticate(tokenCreds));
+    }
+
+    public void testRemoval() throws RepositoryException {
+        String identifier = tokenNode.getIdentifier();
+
+        TokenBasedAuthentication expiredToken = expiredToken();
+        assertFalse(expiredToken.authenticate(tokenCreds));
+
+        try {
+            adminSession.getNodeByIdentifier(identifier);
+            fail("expired token node should be removed.");
+        } catch (ItemNotFoundException e) {
+            // success
+        }
+    }
+
+    public void testInvalidCredentials() throws RepositoryException {
+        try {
+            validTokenAuth.authenticate(creds);
+            fail("RepositoryException expected");
+        } catch (RepositoryException e) {
+            // success
+        }
+
+        try {
+            assertFalse(validTokenAuth.authenticate(simpleCreds));
+            fail("RepositoryException expected");            
+        } catch (RepositoryException e) {
+            // success
+        }
+    }
+
+    public void testAttributes() throws RepositoryException {
+        tokenNode.setProperty(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".any", "correct");
+        adminSession.save();
+        TokenBasedAuthentication auth = new TokenBasedAuthentication(tokenNode.getIdentifier(), TokenBasedAuthentication.TOKEN_EXPIRATION, adminSession);
+
+        assertFalse(auth.authenticate(tokenCreds));
+
+        tokenCreds.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE + ".any", "wrong");
+        assertFalse(auth.authenticate(tokenCreds));
+
+        tokenCreds.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".any", "correct");
+        assertTrue(auth.authenticate(tokenCreds));
+
+        // add informative property
+        tokenNode.setProperty("noMatchRequired", "abc");
+        adminSession.save();
+        auth = new TokenBasedAuthentication(tokenNode.getIdentifier(), TokenBasedAuthentication.TOKEN_EXPIRATION, adminSession);
+
+        assertTrue(auth.authenticate(tokenCreds));
+    }
+
+    public void testUpdateAttributes() throws RepositoryException {
+        tokenNode.setProperty(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".any", "correct");
+        tokenNode.setProperty("informative","value");
+        adminSession.save();
+
+        // token credentials must be updated to contain the additional attribute
+        // present on the token node.
+        TokenBasedAuthentication auth = new TokenBasedAuthentication(tokenNode.getIdentifier(), TokenBasedAuthentication.TOKEN_EXPIRATION, adminSession);
+        tokenCreds.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE + ".any", "correct");
+        assertTrue(auth.authenticate(tokenCreds));               
+        assertEquals("value", tokenCreds.getAttribute("informative"));
+
+        // additional informative property present on credentials upon subsequent
+        // authentication -> the node must not be updated
+        auth = new TokenBasedAuthentication(tokenNode.getIdentifier(), TokenBasedAuthentication.TOKEN_EXPIRATION, adminSession);
+        tokenCreds.setAttribute("informative2", "value2");
+        assertTrue(auth.authenticate(tokenCreds));
+        assertFalse(tokenNode.hasProperty("informative2"));
+
+        // modified informative property present on credentials upon subsequent
+        // authentication -> the node must not be updated
+        auth = new TokenBasedAuthentication(tokenNode.getIdentifier(), TokenBasedAuthentication.TOKEN_EXPIRATION, adminSession);
+        tokenCreds.setAttribute("informative", "otherValue");
+        assertTrue(auth.authenticate(tokenCreds));
+        assertTrue(tokenNode.hasProperty("informative"));
+        assertEquals("value", tokenNode.getProperty("informative").getString());
+
+        // additional mandatory property on the credentials upon subsequent
+        // authentication -> must be ignored
+        auth = new TokenBasedAuthentication(tokenNode.getIdentifier(), TokenBasedAuthentication.TOKEN_EXPIRATION, adminSession);
+        tokenCreds.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".toIgnore", "ignore");
+        assertTrue(auth.authenticate(tokenCreds));
+        assertFalse(tokenNode.hasProperty(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".toIgnore"));
+    }
+
+    public void testIsTokenBasedLogin() {
+        assertFalse(TokenBasedAuthentication.isTokenBasedLogin(simpleCreds));
+        assertFalse(TokenBasedAuthentication.isTokenBasedLogin(creds));
+
+        assertTrue(TokenBasedAuthentication.isTokenBasedLogin(tokenCreds));
+    }
+
+    public void testIsMandatoryAttribute() {
+        assertFalse(TokenBasedAuthentication.isMandatoryAttribute("noMatchRequired"));
+
+        assertTrue(TokenBasedAuthentication.isMandatoryAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE + ".exp"));
+        assertTrue(TokenBasedAuthentication.isMandatoryAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE + ".custom"));
+        assertTrue(TokenBasedAuthentication.isMandatoryAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE + "_custom"));
+        assertTrue(TokenBasedAuthentication.isMandatoryAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE + "custom"));
+    }
+
+    public void testDoCreateToken() {
+        assertFalse(TokenBasedAuthentication.doCreateToken(creds));
+        assertFalse(TokenBasedAuthentication.doCreateToken(simpleCreds));
+        assertFalse(TokenBasedAuthentication.doCreateToken(tokenCreds));
+
+        SimpleCredentials sc = new SimpleCredentials("uid", "pw".toCharArray());
+        sc.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, null);
+
+        assertFalse(TokenBasedAuthentication.doCreateToken(sc));
+
+        sc.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, "somevalue");
+        assertFalse(TokenBasedAuthentication.doCreateToken(sc));
+
+        sc.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, "");
+        assertTrue(TokenBasedAuthentication.doCreateToken(sc));
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationTest.java
index ce08f7a..e9d4c5a 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenBasedAuthenticationTest.java
@@ -16,9 +16,7 @@
  */
 package org.apache.jackrabbit.core.security.authentication.token;
 
-import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
-import org.apache.jackrabbit.test.AbstractJCRTest;
-
+import java.util.UUID;
 import javax.jcr.Credentials;
 import javax.jcr.ItemNotFoundException;
 import javax.jcr.Node;
@@ -27,45 +25,87 @@ import javax.jcr.SimpleCredentials;
 import javax.jcr.lock.LockException;
 import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.version.VersionException;
-import java.util.Calendar;
-import java.util.Date;
+
+import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.test.AbstractJCRTest;
 
 /**
- * <code>TokenBasedAuthenticationTest</code>...
+ * <code>TokenBasedAuthenticationOakTest</code>...
  */
 public class TokenBasedAuthenticationTest extends AbstractJCRTest {
 
-    Node tokenNode;
+    private SessionImpl adminSession;
+    private User testUser;
+
+    private String token;
+    private Node tokenNode;
+    private TokenCredentials tokenCreds;
 
-    TokenBasedAuthentication nullTokenAuth;
-    TokenBasedAuthentication validTokenAuth;
+    private String expiredToken;
+    private Node expiredNode;
+    private TokenCredentials expiredCreds;
 
-    TokenCredentials tokenCreds;
-    Credentials simpleCreds = new SimpleCredentials("uid", "pw".toCharArray());
-    Credentials creds = new Credentials() {};
+
+    private TokenBasedAuthentication nullTokenAuth;
+    private TokenBasedAuthentication validTokenAuth;
+
+    private Credentials simpleCreds = new SimpleCredentials("uid", "pw".toCharArray());
+    private Credentials creds = new Credentials() {};
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
 
-        tokenNode = testRootNode.addNode(nodeName1, "nt:unstructured");
-        tokenNode.setProperty(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".exp", new Date().getTime()+TokenBasedAuthentication.TOKEN_EXPIRATION);
-        superuser.save();
+        adminSession = (SessionImpl) getHelper().getSuperuserSession("security");
+        testUser = adminSession.getUserManager().createUser(UUID.randomUUID().toString(), "pw");
+        adminSession.save();
 
-        String token = tokenNode.getIdentifier();
+        SimpleCredentials sc = new SimpleCredentials(testUser.getID(), "pw".toCharArray());
+        sc.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, "");
+        sc.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE+".any", "correct");
+        sc.setAttribute("informative", "value");
+
+        TokenProvider tp = new TokenProvider(adminSession, TokenBasedAuthentication.TOKEN_EXPIRATION);
+        TokenInfo ti = tp.createToken(testUser, sc);
+        tokenCreds = ti.getCredentials();
+        token = tokenCreds.getToken();
+        tokenNode = TokenProvider.getTokenNode(token, adminSession);
+
+        long ttl = 1; // 1ms expiration
+        tp = new TokenProvider(adminSession, ttl);
+        TokenInfo expired = tp.createToken(testUser, sc);
+        expiredCreds = expired.getCredentials();
+        expiredToken = expiredCreds.getToken();
+        long tokenWillExpireAfter = System.currentTimeMillis() + ttl;
+        expiredNode = TokenProvider.getTokenNode(expiredToken, adminSession);
+
+        nullTokenAuth = new TokenBasedAuthentication(null, -1, adminSession);
+        validTokenAuth = new TokenBasedAuthentication(token, 7200, adminSession);
+
+        while (System.currentTimeMillis() <= tokenWillExpireAfter) {
+            // wait until the token is actually expired
+        }
+    }
 
-        nullTokenAuth = new TokenBasedAuthentication(null, -1, superuser);
-        validTokenAuth = new TokenBasedAuthentication(token, 7200, superuser);
+    @Override
+    protected void tearDown() throws Exception {
+        try {
+            testUser.remove();
+            adminSession.save();
+            adminSession.logout();
+        } finally {
+            super.tearDown();
+        }
+    }
 
-        tokenCreds = new TokenCredentials(token);
+    private TokenBasedAuthentication createAuthenticationForExpiredToken() throws RepositoryException, LockException, ConstraintViolationException, VersionException {
+        return new TokenBasedAuthentication(expiredToken, TokenBasedAuthentication.TOKEN_EXPIRATION, adminSession);
     }
 
-    private TokenBasedAuthentication expiredToken() throws RepositoryException, LockException, ConstraintViolationException, VersionException {
-        Calendar cal = Calendar.getInstance();
-        cal.setTimeInMillis(new Date().getTime()-100);
-        tokenNode.setProperty(".token.exp", cal);
-        superuser.save();
-        return new TokenBasedAuthentication(tokenNode.getIdentifier(), TokenBasedAuthentication.TOKEN_EXPIRATION, superuser);
+    private TokenBasedAuthentication createAuthentication() throws RepositoryException {
+        return new TokenBasedAuthentication(token, TokenBasedAuthentication.TOKEN_EXPIRATION, adminSession);
     }
 
     public void testCanHandle() throws RepositoryException {
@@ -77,23 +117,23 @@ public class TokenBasedAuthenticationTest extends AbstractJCRTest {
 
         assertFalse(validTokenAuth.canHandle(creds));
         assertFalse(nullTokenAuth.canHandle(creds));
+    }
 
-        TokenBasedAuthentication expiredToken = expiredToken();
-        assertTrue(expiredToken.canHandle(tokenCreds));
+    public void testCanHandleExpiredToken() throws RepositoryException {
+        TokenBasedAuthentication expiredToken = createAuthenticationForExpiredToken();
+        assertTrue(expiredToken.canHandle(expiredCreds));
     }
 
     public void testExpiry() throws RepositoryException {
-        assertTrue(validTokenAuth.authenticate(tokenCreds));
-
-        TokenBasedAuthentication expiredToken = expiredToken();
-        assertFalse(expiredToken.authenticate(tokenCreds));
+        TokenBasedAuthentication expiredToken = createAuthenticationForExpiredToken();
+        assertFalse(expiredToken.authenticate(expiredCreds));
     }
 
     public void testRemoval() throws RepositoryException {
-        String identifier = tokenNode.getIdentifier();
+        String identifier = expiredNode.getIdentifier();
 
-        TokenBasedAuthentication expiredToken = expiredToken();
-        assertFalse(expiredToken.authenticate(tokenCreds));
+        TokenBasedAuthentication expiredToken = createAuthenticationForExpiredToken();
+        assertFalse(expiredToken.authenticate(expiredCreds));
 
         try {
             superuser.getNodeByIdentifier(identifier);
@@ -120,57 +160,48 @@ public class TokenBasedAuthenticationTest extends AbstractJCRTest {
     }
 
     public void testAttributes() throws RepositoryException {
-        tokenNode.setProperty(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".any", "correct");
-        superuser.save();
-        TokenBasedAuthentication auth = new TokenBasedAuthentication(tokenNode.getIdentifier(), TokenBasedAuthentication.TOKEN_EXPIRATION, superuser);
+        TokenBasedAuthentication auth = createAuthentication();
+        assertFalse(auth.authenticate(new TokenCredentials(token)));
 
-        assertFalse(auth.authenticate(tokenCreds));
-
-        tokenCreds.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".any", "wrong");
-        assertFalse(auth.authenticate(tokenCreds));
-
-        tokenCreds.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".any", "correct");
-        assertTrue(auth.authenticate(tokenCreds));
-
-        // add informative property
-        tokenNode.setProperty("noMatchRequired", "abc");
-        superuser.save();
-        auth = new TokenBasedAuthentication(tokenNode.getIdentifier(), TokenBasedAuthentication.TOKEN_EXPIRATION, superuser);
+        TokenCredentials tc = new TokenCredentials(token);
+        tc.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".any", "wrong");
+        assertFalse(auth.authenticate(tc));
 
+        tc = new TokenCredentials(token);
+        tc.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".any", "correct");
         assertTrue(auth.authenticate(tokenCreds));
     }
 
     public void testUpdateAttributes() throws RepositoryException {
-        tokenNode.setProperty(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".any", "correct");
-        tokenNode.setProperty("informative","value");
-        superuser.save();
-
         // token credentials must be updated to contain the additional attribute
         // present on the token node.
-        TokenBasedAuthentication auth = new TokenBasedAuthentication(tokenNode.getIdentifier(), TokenBasedAuthentication.TOKEN_EXPIRATION, superuser);
-        tokenCreds.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".any", "correct");
-        assertTrue(auth.authenticate(tokenCreds));               
-        assertEquals("value", tokenCreds.getAttribute("informative"));
+        TokenBasedAuthentication auth = createAuthentication();
+
+        TokenCredentials tc = new TokenCredentials(token);
+        tc.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".any", "correct");
+
+        assertTrue(auth.authenticate(tc));
+        assertEquals("value", tc.getAttribute("informative"));
 
         // additional informative property present on credentials upon subsequent
         // authentication -> the node must not be updated
-        auth = new TokenBasedAuthentication(tokenNode.getIdentifier(), TokenBasedAuthentication.TOKEN_EXPIRATION, superuser);
-        tokenCreds.setAttribute("informative2", "value2");
-        assertTrue(auth.authenticate(tokenCreds));
+        auth = createAuthentication();
+        tc.setAttribute("informative2", "value2");
+        assertTrue(auth.authenticate(tc));
         assertFalse(tokenNode.hasProperty("informative2"));
 
         // modified informative property present on credentials upon subsequent
         // authentication -> the node must not be updated
-        auth = new TokenBasedAuthentication(tokenNode.getIdentifier(), TokenBasedAuthentication.TOKEN_EXPIRATION, superuser);
-        tokenCreds.setAttribute("informative", "otherValue");
-        assertTrue(auth.authenticate(tokenCreds));
+        auth = createAuthentication();
+        tc.setAttribute("informative", "otherValue");
+        assertTrue(auth.authenticate(tc));
         assertTrue(tokenNode.hasProperty("informative"));
         assertEquals("value", tokenNode.getProperty("informative").getString());
 
         // additional mandatory property on the credentials upon subsequent
         // authentication -> must be ignored
-        auth = new TokenBasedAuthentication(tokenNode.getIdentifier(), TokenBasedAuthentication.TOKEN_EXPIRATION, superuser);        
-        tokenCreds.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".toIgnore", "ignore");
+        auth = createAuthentication();
+        tc.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".toIgnore", "ignore");
         assertTrue(auth.authenticate(tokenCreds));
         assertFalse(tokenNode.hasProperty(TokenBasedAuthentication.TOKEN_ATTRIBUTE +".toIgnore"));
     }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenProviderTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenProviderTest.java
new file mode 100644
index 0000000..0beccb4
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authentication/token/TokenProviderTest.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.security.authentication.token;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.jackrabbit.test.NotExecutableException;
+
+public class TokenProviderTest extends AbstractJCRTest {
+
+    private User testuser;
+    private String userId;
+
+    private SessionImpl session;
+    private TokenProvider tokenProvider;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        if (superuser instanceof SessionImpl) {
+            UserManager umgr = ((SessionImpl) superuser).getUserManager();
+            if (!umgr.isAutoSave()) {
+                umgr.autoSave(true);
+            }
+            String uid = "test";
+            while (umgr.getAuthorizable(uid) != null) {
+                uid += "_";
+            }
+
+            testuser = umgr.createUser(uid, uid);
+            userId = testuser.getID();
+        } else {
+            throw new NotExecutableException();
+        }
+
+        if (superuser.nodeExists(((ItemBasedPrincipal) testuser.getPrincipal()).getPath())) {
+            session = (SessionImpl) superuser;
+        } else {
+            session = (SessionImpl) getHelper().getSuperuserSession("security");
+        }
+        tokenProvider = new TokenProvider((SessionImpl) session, TokenBasedAuthentication.TOKEN_EXPIRATION);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        try {
+            testuser.remove();
+            session.logout();
+        } finally {
+            super.tearDown();
+        }
+    }
+
+    public void testCreateTokenFromInvalidCredentials() throws Exception {
+        assertNull(tokenProvider.createToken(testuser, new SimpleCredentials("unknownUserId", new char[0])));
+    }
+
+    public void testCreateTokenFromCredentials() throws Exception {
+        TokenInfo info = tokenProvider.createToken(testuser, new SimpleCredentials(userId, new char[0]));
+        assertTokenInfo(info);
+    }
+
+    public void testCreateTokenIsCaseInsensitive() throws Exception {
+        String upperCaseUserId = userId.toUpperCase();
+        TokenInfo info = tokenProvider.createToken(testuser, new SimpleCredentials(upperCaseUserId, new char[0]));
+        assertTokenInfo(info);
+    }
+
+    public void testTokenNode() throws Exception {
+        Map<String, String> privateAttributes = new HashMap<String, String>();
+        privateAttributes.put(".token_exp", "value");
+        privateAttributes.put(".tokenTest", "value");
+        privateAttributes.put(".token_something", "value");
+
+        Map<String, String> publicAttributes = new HashMap<String, String>();
+        publicAttributes.put("any", "value");
+        publicAttributes.put("another", "value");
+
+        Map<String, String> attributes = new HashMap<String, String>();
+        attributes.putAll(publicAttributes);
+        attributes.putAll(privateAttributes);
+
+        SimpleCredentials sc = new SimpleCredentials(userId, userId.toCharArray());
+        for (String s : attributes.keySet()) {
+            sc.setAttribute(s, attributes.get(s));
+        }
+
+        TokenInfo info = tokenProvider.createToken(testuser, sc);
+        Node tokenNode = getTokenNode(info);
+        Property prop = tokenNode.getProperty("rep:token.key");
+        assertNotNull(prop);
+        assertEquals(PropertyType.STRING, prop.getType());
+        assertTrue(prop.getDefinition().isProtected());
+
+        prop = tokenNode.getProperty("rep:token.exp");
+        assertNotNull(prop);
+        assertEquals(PropertyType.DATE, prop.getType());
+        assertTrue(prop.getDefinition().isProtected());
+
+        for (String key : privateAttributes.keySet()) {
+            assertEquals(privateAttributes.get(key), tokenNode.getProperty(key).getString());
+        }
+
+        for (String key : publicAttributes.keySet()) {
+            assertEquals(publicAttributes.get(key), tokenNode.getProperty(key).getString());
+        }
+    }
+
+    public void testGetTokenInfoFromInvalidToken() throws Exception {
+        List<String> invalid = new ArrayList<String>();
+        invalid.add("/invalid");
+        invalid.add(UUID.randomUUID().toString());
+
+        try {
+            for (String token : invalid) {
+                TokenInfo info = tokenProvider.getTokenInfo(token);
+                assertNull(info);
+            }
+        } catch (Exception e) {
+            // success
+        }
+    }
+
+    public void testGetTokenInfo() throws Exception {
+        String token = tokenProvider.createToken(testuser, new SimpleCredentials(userId, userId.toCharArray())).getToken();
+        TokenInfo info = tokenProvider.getTokenInfo(token);
+        assertTokenInfo(info);
+    }
+
+    public void testIsExpired() throws Exception {
+        TokenInfo info = tokenProvider.createToken(testuser, new SimpleCredentials(userId, userId.toCharArray()));
+
+        long loginTime = waitForSystemTimeIncrement(System.currentTimeMillis());
+        assertFalse(info.isExpired(loginTime));
+        assertTrue(info.isExpired(loginTime + TokenBasedAuthentication.TOKEN_EXPIRATION));
+    }
+
+    public void testReset() throws Exception {
+        TokenInfo info = tokenProvider.createToken(testuser, new SimpleCredentials(userId, userId.toCharArray()));
+        long expTime = getTokenNode(info).getProperty("rep:token.exp").getLong();
+
+        long loginTime = waitForSystemTimeIncrement(System.currentTimeMillis());
+        assertFalse(info.resetExpiration(loginTime));
+        assertFalse(info.resetExpiration(loginTime + TokenBasedAuthentication.TOKEN_EXPIRATION));
+
+        assertTrue(info.resetExpiration(loginTime + TokenBasedAuthentication.TOKEN_EXPIRATION / 2));
+        long expTime2 = getTokenNode(info).getProperty("rep:token.exp").getLong();
+        assertFalse(expTime == expTime2);
+    }
+
+    //--------------------------------------------------------------------------
+    private static void assertTokenInfo(TokenInfo info) {
+        assertNotNull(info);
+        assertNotNull(info.getToken());
+        assertFalse(info.isExpired(new Date().getTime()));
+    }
+
+    private Node getTokenNode(TokenInfo info) throws RepositoryException {
+        return TokenProvider.getTokenNode(info.getToken(), session);
+    }
+
+    private static long waitForSystemTimeIncrement(long old){
+        while (old == System.currentTimeMillis()) {
+            // wait for system timer to move
+        }
+        return System.currentTimeMillis();
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractACLTemplateTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractACLTemplateTest.java
index 79805f2..1af9034 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractACLTemplateTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractACLTemplateTest.java
@@ -125,6 +125,11 @@ public abstract class AbstractACLTemplateTest extends AbstractAccessControlTest
                 public Value getRestriction(String restrictionName) {
                     return null;
                 }
+
+                public Value[] getRestrictions(String restrictionName) throws RepositoryException {
+                    return null;
+                }
+
                 public Principal getPrincipal() {
                     return testPrincipal;
                 }
@@ -159,6 +164,11 @@ public abstract class AbstractACLTemplateTest extends AbstractAccessControlTest
                 public Value getRestriction(String restrictionName) {
                     return null;
                 }
+
+                public Value[] getRestrictions(String restrictionName) throws RepositoryException {
+                    return null;
+                }
+
                 public Principal getPrincipal() {
                     return testPrincipal;
                 }
@@ -322,4 +332,4 @@ public abstract class AbstractACLTemplateTest extends AbstractAccessControlTest
             // success
         }
     }
-}
\ No newline at end of file
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractEntryTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractEntryTest.java
index 5eb305d..e89b116 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractEntryTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractEntryTest.java
@@ -205,6 +205,11 @@ public abstract class AbstractEntryTest extends AbstractAccessControlTest {
             public Value getRestriction(String restrictionName) {
                 return null;
             }
+
+            public Value[] getRestrictions(String restrictionName) throws RepositoryException {
+                return null;
+            }
+
             public Principal getPrincipal() {
                 return testPrincipal;
             }
@@ -288,6 +293,11 @@ public abstract class AbstractEntryTest extends AbstractAccessControlTest {
             public Value getRestriction(String restrictionName) {
                 return null;
             }
+
+            public Value[] getRestrictions(String restrictionName) throws RepositoryException {
+                return null;
+            }
+
             public Principal getPrincipal() {
                 return testPrincipal;
             }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractLockManagementTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractLockManagementTest.java
index bb6df1a..30cbcca 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractLockManagementTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractLockManagementTest.java
@@ -22,7 +22,9 @@ import org.apache.jackrabbit.test.NotExecutableException;
 import javax.jcr.AccessDeniedException;
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
+import javax.jcr.Session;
 import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockManager;
 
 /** <code>AbstractVersionAccessTest</code>... */
 public abstract class AbstractLockManagementTest extends AbstractEvaluationTest {
@@ -129,4 +131,49 @@ public abstract class AbstractLockManagementTest extends AbstractEvaluationTest
         boolean isLocked = n.isLocked();
         assertFalse(isLocked);
     }
+
+    public void testLockBreaking() throws RepositoryException, NotExecutableException {
+        String locktoken = null;
+        LockManager sulm = superuser.getWorkspace().getLockManager();
+        String lockedpath = null;
+
+        try {
+            Node trn = getTestNode();
+            modifyPrivileges(trn.getPath(), Privilege.JCR_READ, true);
+            modifyPrivileges(trn.getPath(), PrivilegeRegistry.REP_WRITE, true);
+            modifyPrivileges(trn.getPath(), Privilege.JCR_LOCK_MANAGEMENT, true);
+
+            Session lockingSession = trn.getSession();
+
+            assertFalse("super user and test user should have different user ids: " + lockingSession.getUserID() + " vs " + superuser.getUserID(),
+                    lockingSession.getUserID().equals(superuser.getUserID()));
+
+            trn.addNode("locktest", "nt:unstructured");
+            trn.addMixin("mix:lockable");
+            lockingSession.save();
+
+            // let the "other" user lock the node
+            LockManager oulm = lockingSession.getWorkspace().getLockManager();
+            Lock l = oulm.lock(trn.getPath(), true, false, Long.MAX_VALUE, null);
+            lockedpath = trn.getPath();
+            locktoken = l.getLockToken();
+            lockingSession.logout();
+
+            // transfer the lock token to the super user and try the unlock
+
+            Node lockednode = superuser.getNode(lockedpath);
+            assertTrue(lockednode.isLocked());
+            Lock sl = sulm.getLock(lockedpath);
+            assertNotNull(sl.getLockToken());
+            sulm.addLockToken(sl.getLockToken());
+            sulm.unlock(lockedpath);
+            locktoken = null;
+        }
+        finally {
+            if (locktoken != null && lockedpath != null) {
+                sulm.addLockToken(locktoken);
+                sulm.unlock(lockedpath);
+            }
+        }
+    }
 }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractRepositoryOperationTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractRepositoryOperationTest.java
index a6f3fa1..c8de108 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractRepositoryOperationTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/AbstractRepositoryOperationTest.java
@@ -457,9 +457,10 @@ public abstract class AbstractRepositoryOperationTest extends AbstractEvaluation
             assertNotNull(aces);
             assertEquals(2, aces.length);
 
-            // change the policy
+            // change the policy: removing the second entry in the access control list
             acl = (AccessControlList) acMgr.getPolicies(null)[0];
-            acl.removeAccessControlEntry(aces[0]);
+            AccessControlEntry toRemove = acl.getAccessControlEntries()[1];
+            acl.removeAccessControlEntry(toRemove);
             acMgr.setPolicy(null, acl);
             superuser.save();
 
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplateEntryTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplateEntryTest.java
new file mode 100644
index 0000000..09dd992
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplateEntryTest.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.security.authorization.acl;
+
+import java.security.Principal;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.api.JackrabbitWorkspace;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
+import org.apache.jackrabbit.api.security.authorization.PrivilegeManager;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.security.authorization.AbstractEntryTest;
+import org.apache.jackrabbit.test.NotExecutableException;
+
+/**
+ * <code>EntryTest</code>...
+ */
+public class ACLTemplateEntryTest extends AbstractEntryTest {
+
+    private ACLTemplate acl;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        SessionImpl s = (SessionImpl) superuser;
+        PrivilegeManager privMgr = ((JackrabbitWorkspace) superuser.getWorkspace()).getPrivilegeManager();
+
+        acl = new ACLTemplate(testPath, s.getPrincipalManager(), privMgr, s.getValueFactory(), s, false);
+    }
+
+    @Override
+    protected JackrabbitAccessControlEntry createEntry(Principal principal, Privilege[] privileges, boolean isAllow)
+            throws RepositoryException {
+        return acl.createEntry(principal, privileges, isAllow, Collections.<String, Value>emptyMap());
+    }
+
+    @Override
+    protected JackrabbitAccessControlEntry createEntry(Principal principal, Privilege[] privileges, boolean isAllow, Map<String, Value> restrictions) throws RepositoryException {
+        return acl.createEntry(principal, privileges, isAllow, restrictions);
+    }
+
+    @Override
+    protected JackrabbitAccessControlEntry createEntryFromBase(JackrabbitAccessControlEntry base, Privilege[] privileges, boolean isAllow) throws RepositoryException, NotExecutableException {
+        if (base instanceof ACLTemplate.Entry) {
+            return acl.createEntry((ACLTemplate.Entry) base, privileges, isAllow);
+        } else {
+            throw new NotExecutableException();
+        }
+    }
+
+    @Override
+    protected Map<String, Value> getTestRestrictions() throws RepositoryException {
+        String restrName = ((SessionImpl) superuser).getJCRName(ACLTemplate.P_GLOB);
+        return Collections.singletonMap(restrName, superuser.getValueFactory().createValue("/.*"));        
+    }
+
+    public void testRestrictions() throws RepositoryException {
+        // test if restrictions with expanded name are properly resolved
+        Map<String, Value> restrictions = new HashMap<String,Value>();
+        restrictions.put(ACLTemplate.P_GLOB.toString(), superuser.getValueFactory().createValue("*/test"));
+
+        Privilege[] privs = new Privilege[] {acMgr.privilegeFromName(Privilege.JCR_ALL)};
+        ACLTemplate.Entry ace = acl.createEntry(testPrincipal, privs, true, restrictions);
+
+        Value v = ace.getRestriction(ACLTemplate.P_GLOB.toString());
+        Value v2 = ace.getRestriction(((SessionImpl) superuser).getJCRName(ACLTemplate.P_GLOB));
+        assertEquals(v, v2);
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplateTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplateTest.java
index 44665df..8935ec7 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplateTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/ACLTemplateTest.java
@@ -56,7 +56,7 @@ public class ACLTemplateTest extends AbstractACLTemplateTest {
     @Override
     protected JackrabbitAccessControlList createEmptyTemplate(String path) throws RepositoryException {
         SessionImpl sImpl = (SessionImpl) superuser;
-        return new ACLTemplate(path, principalMgr, privilegeMgr, sImpl.getValueFactory(), sImpl);
+        return new ACLTemplate(path, principalMgr, privilegeMgr, sImpl.getValueFactory(), sImpl, false);
     }
 
     @Override
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/EntryCollectorTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/EntryCollectorTest.java
index bd00d15..237ac71 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/EntryCollectorTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/EntryCollectorTest.java
@@ -16,33 +16,33 @@
  */
 package org.apache.jackrabbit.core.security.authorization.acl;
 
-import org.apache.jackrabbit.api.JackrabbitSession;
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
-import org.apache.jackrabbit.api.security.user.Group;
-import org.apache.jackrabbit.api.security.user.User;
-import org.apache.jackrabbit.api.security.user.UserManager;
-import org.apache.jackrabbit.core.security.TestPrincipal;
-import org.apache.jackrabbit.test.NotExecutableException;
-import org.apache.jackrabbit.test.api.security.AbstractAccessControlTest;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
 
 import javax.jcr.AccessDeniedException;
 import javax.jcr.Node;
-import javax.jcr.Property;
+import javax.jcr.NodeIterator;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
-import javax.jcr.security.AccessControlEntry;
 import javax.jcr.security.AccessControlList;
 import javax.jcr.security.AccessControlManager;
 import javax.jcr.security.AccessControlPolicy;
 import javax.jcr.security.AccessControlPolicyIterator;
 import javax.jcr.security.Privilege;
-import java.security.Principal;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.UUID;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.core.security.TestPrincipal;
+import org.apache.jackrabbit.test.NotExecutableException;
+import org.apache.jackrabbit.test.api.security.AbstractAccessControlTest;
 
 /**
  * <code>EntryCollectorTest</code>...
@@ -238,48 +238,6 @@ public class EntryCollectorTest extends AbstractAccessControlTest {
         return names;
     }
 
-    public void testEntriesAreCached() throws Exception {
-        modifyPrivileges(path, testGroup.getPrincipal(), privilegesFromName(Privilege.JCR_READ), true);
-        AccessControlPolicy[] plcs = acMgr.getEffectivePolicies(path);
-        AccessControlPolicy[] plcs2 = acMgr.getEffectivePolicies(childNPath);
-
-        // ACEs must be the same on path and childPath as the entries are
-        // obtained from the cache
-        assertTrue(Arrays.equals(plcs, plcs2));
-        assertEquals(plcs.length, plcs2.length);
-        for (int i = 0; i < plcs.length; i++) {
-            if (plcs[i] instanceof AccessControlList) {
-                assertTrue(plcs2[i] instanceof AccessControlList);
-
-                AccessControlEntry[] aces = ((AccessControlList) plcs[0]).getAccessControlEntries();
-                AccessControlEntry[] aces2 = ((AccessControlList) plcs2[0]).getAccessControlEntries();
-                for (int j = 0; j < aces.length; j++) {
-                    assertTrue(aces[j] == aces2[j]);
-                }
-            } else {
-                assertEquals(plcs[i].getClass(), plcs2[i].getClass());
-            }
-        }
-
-
-        // retrieve effective policies for path again
-        // -> test if aces are retrieved from the cache and thus refer to the same objects.
-        AccessControlPolicy[] plcs3 = acMgr.getEffectivePolicies(path);
-        for (int i = 0; i < plcs.length; i++) {
-            if (plcs[i] instanceof AccessControlList) {
-                assertTrue(plcs3[i] instanceof AccessControlList);
-
-                AccessControlEntry[] aces = ((AccessControlList) plcs[0]).getAccessControlEntries();
-                AccessControlEntry[] aces3 = ((AccessControlList) plcs3[0]).getAccessControlEntries();
-                for (int j = 0; j < aces.length; j++) {
-                    assertTrue(aces[j] == aces3[j]);
-                }
-            } else {
-                assertEquals(plcs[i].getClass(), plcs2[i].getClass());
-            }
-        }
-    }
-
     public void testPermissions() throws Exception {
         Session superuser2 = getHelper().getSuperuserSession();
         try {
@@ -362,6 +320,112 @@ public class EntryCollectorTest extends AbstractAccessControlTest {
         } finally {
             superuser2.logout();
         }
-        
     }
+
+    static interface TestInvokation {
+        public void runTest() throws Exception;
+    }
+
+    private void runTestUnderLoad(TestInvokation ti) throws Exception {
+
+        JcrTestThread t[] = new JcrTestThread[4];
+
+        for (int i = 0; i < t.length; i++) {
+            t[i] = new JcrTestThread();
+        }
+
+        try {
+            for (int i = 0; i < t.length; i++) {
+                t[i].start();
+            }
+            ti.runTest();
+        }
+        finally {
+            for (int i = 0; i < t.length; i++) {
+                t[i].stopMe();
+                t[i].join();
+                Throwable th = t[i].getLastExc();
+                if (th != null) {
+                    fail("failure in load thread: " + th);
+                }
+            }
+        }
+    }
+
+    public void testCacheUnderLoad() throws Exception {
+        runTestUnderLoad(new TestInvokation() {
+            public void runTest() throws Exception {
+                testCache();
+            }
+        });
+    }
+
+    public void testPermissionsUnderLoad() throws Exception {
+        runTestUnderLoad(new TestInvokation() {
+            public void runTest() throws Exception {
+                testPermissions();
+            }
+        });
+    }
+
+    /**
+     * Test code that that walks the repository.
+     */
+    private class JcrTestThread extends Thread {
+
+        private boolean stopme = false;
+        private Throwable lastErr;
+
+        @Override
+        public void run() {
+            while (!this.stopme) {
+                Session session = null;
+                try {
+                    session = getHelper().getReadOnlySession();
+                    walk(session.getRootNode());
+                }
+                catch (RepositoryException ex) {
+                    // ignored
+                } catch (Throwable ex) {
+                    lastErr = ex;
+                }
+                finally {
+                    if (session != null) {
+                        session.logout();
+                        session = null;
+                    }
+                }
+            }
+        }
+
+        public void stopMe() {
+            this.stopme = true;
+        }
+
+        public Throwable getLastExc() {
+            return lastErr;
+        }
+
+        private void walk(Node node) {
+            if (stopme) {
+                return;
+            }
+
+            try {
+                if ("/jcr:system".equals(node.getPath())) {
+                    // do not descend into an non-interesting subtree
+                    return;
+                }
+
+                NodeIterator ni = node.getNodes();
+                while (ni.hasNext()) {
+                    walk(ni.nextNode());
+                }
+            } catch (RepositoryException ex) {
+                // ignore
+            } catch (Throwable ex) {
+                lastErr = ex;
+            }
+        }
+   }
 }
\ No newline at end of file
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/EntryTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/EntryTest.java
index b186e49..684c031 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/EntryTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/EntryTest.java
@@ -16,113 +16,87 @@
  */
 package org.apache.jackrabbit.core.security.authorization.acl;
 
-import org.apache.jackrabbit.api.JackrabbitWorkspace;
-import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
-import org.apache.jackrabbit.api.security.authorization.PrivilegeManager;
-import org.apache.jackrabbit.core.NodeImpl;
-import org.apache.jackrabbit.core.SessionImpl;
-import org.apache.jackrabbit.core.id.NodeId;
-import org.apache.jackrabbit.core.security.authorization.AbstractEntryTest;
-import org.apache.jackrabbit.test.NotExecutableException;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Value;
-import javax.jcr.security.AccessControlPolicy;
-import javax.jcr.security.AccessControlPolicyIterator;
-import javax.jcr.security.Privilege;
 import java.security.Principal;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import javax.jcr.AccessDeniedException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.security.authorization.AbstractEvaluationTest;
+import org.apache.jackrabbit.test.NotExecutableException;
 
 /**
  * <code>EntryTest</code>...
  */
-public class EntryTest extends AbstractEntryTest {
+public class EntryTest extends AbstractEvaluationTest {
 
-    private ACLTemplate acl;
+    private String testPath;
+    private JackrabbitAccessControlList acl;
 
-    @Override
     protected void setUp() throws Exception {
         super.setUp();
-
-        SessionImpl s = (SessionImpl) superuser;
-        PrivilegeManager privMgr = ((JackrabbitWorkspace) superuser.getWorkspace()).getPrivilegeManager();
-
-        acl = new ACLTemplate(testPath, s.getPrincipalManager(), privMgr, s.getValueFactory(), s);
+        testPath = testRootNode.getPath();
     }
 
     @Override
-    protected JackrabbitAccessControlEntry createEntry(Principal principal, Privilege[] privileges, boolean isAllow)
-            throws RepositoryException {
-        return acl.createEntry(principal, privileges, isAllow, Collections.<String, Value>emptyMap());
+    protected void tearDown() throws Exception {
+        try {
+            acMgr.removePolicy(testPath, acl);
+            superuser.save();
+        } finally {
+            super.tearDown();
+        }
     }
 
     @Override
-    protected JackrabbitAccessControlEntry createEntry(Principal principal, Privilege[] privileges, boolean isAllow, Map<String, Value> restrictions) throws RepositoryException {
-        return acl.createEntry(principal, privileges, isAllow, restrictions);
+    protected boolean isExecutable() {
+        return EvaluationUtil.isExecutable(acMgr);
     }
 
     @Override
-    protected JackrabbitAccessControlEntry createEntryFromBase(JackrabbitAccessControlEntry base, Privilege[] privileges, boolean isAllow) throws RepositoryException, NotExecutableException {
-        if (base instanceof ACLTemplate.Entry) {
-            return acl.createEntry((ACLTemplate.Entry) base, privileges, isAllow);
-        } else {
-            throw new NotExecutableException();
-        }
+    protected JackrabbitAccessControlList getPolicy(AccessControlManager acM, String path, Principal principal) throws RepositoryException, AccessDeniedException, NotExecutableException {
+        return EvaluationUtil.getPolicy(acM, path, principal);
     }
 
     @Override
-    protected Map<String, Value> getTestRestrictions() throws RepositoryException {
-        String restrName = ((SessionImpl) superuser).getJCRName(ACLTemplate.P_GLOB);
-        return Collections.singletonMap(restrName, superuser.getValueFactory().createValue("/.*"));        
+    protected Map<String, Value> getRestrictions(Session s, String path) {
+        return Collections.emptyMap();
     }
 
     public void testIsLocal() throws NotExecutableException, RepositoryException {
-        ACLTemplate.Entry entry = (ACLTemplate.Entry) createEntry(new String[] {Privilege.JCR_READ}, true);
-
-        // false since acl has been created from path only -> no id
-        assertFalse(entry.isLocal(((NodeImpl) testRootNode).getNodeId()));
-        // false since internal id is null -> will never match.
-        assertFalse(entry.isLocal(NodeId.randomId()));
-    }
+        acl = getPolicy(acMgr, testPath, testUser.getPrincipal());
+        modifyPrivileges(testPath, Privilege.JCR_READ, true);
 
-    public void testIsLocal2()  throws NotExecutableException, RepositoryException {
-        String path = testRootNode.getPath();
-        AccessControlPolicy[] acls = acMgr.getPolicies(path);
-        if (acls.length == 0) {
-            AccessControlPolicyIterator it = acMgr.getApplicablePolicies(path);
-            if (!it.hasNext()) {
-                throw new NotExecutableException();
-            }
-            acMgr.setPolicy(path, it.nextAccessControlPolicy());
-            acls = acMgr.getPolicies(path);
-        }
-
-        assertTrue(acls[0] instanceof ACLTemplate);
-
-        ACLTemplate acl = (ACLTemplate) acls[0];
-        assertEquals(path, acl.getPath());
-
-        ACLTemplate.Entry entry = acl.createEntry(testPrincipal, new Privilege[] {acMgr.privilegeFromName(Privilege.JCR_READ)}, true, Collections.<String,Value>emptyMap());
+        NodeImpl aclNode = (NodeImpl) superuser.getNode(acl.getPath() + "/rep:policy");
+        List<Entry> entries = Entry.readEntries(aclNode, testRootNode.getPath());
+        assertTrue(!entries.isEmpty());
+        assertEquals(1, entries.size());
 
-        // node is must be present + must match to testrootnodes id.
+        Entry entry = entries.iterator().next();
+        // false since acl has been created from path only -> no id
         assertTrue(entry.isLocal(((NodeImpl) testRootNode).getNodeId()));
-        // but not to a random id.
+        // false since internal id is null -> will never match.
         assertFalse(entry.isLocal(NodeId.randomId()));
     }
 
-    public void testRestrictions() throws RepositoryException {
+    public void testRestrictions() throws RepositoryException, NotExecutableException {
         // test if restrictions with expanded name are properly resolved
         Map<String, Value> restrictions = new HashMap<String,Value>();
         restrictions.put(ACLTemplate.P_GLOB.toString(), superuser.getValueFactory().createValue("*/test"));
 
-        Privilege[] privs = new Privilege[] {acMgr.privilegeFromName(Privilege.JCR_ALL)};
-        ACLTemplate.Entry ace = acl.createEntry(testPrincipal, privs, true, restrictions);
-
-        Value v = ace.getRestriction(ACLTemplate.P_GLOB.toString());
-        Value v2 = ace.getRestriction(((SessionImpl) superuser).getJCRName(ACLTemplate.P_GLOB));
-        assertEquals(v, v2);
+        acl = getPolicy(acMgr, testPath, testUser.getPrincipal());
+        acl.addEntry(testUser.getPrincipal(), new Privilege[] {acMgr.privilegeFromName(Privilege.JCR_ALL)}, true, restrictions);
+        acMgr.setPolicy(testPath, acl);
+        superuser.save();
 
         Map<String, Boolean> toMatch = new HashMap<String, Boolean>();
         toMatch.put(acl.getPath(), false);
@@ -132,8 +106,14 @@ public class EntryTest extends AbstractEntryTest {
         toMatch.put(acl.getPath() + "/something/test", true);
         toMatch.put(acl.getPath() + "de/test", true);
 
+        NodeImpl aclNode = (NodeImpl) superuser.getNode(acl.getPath() + "/rep:policy");
+        List<Entry> entries = Entry.readEntries(aclNode, testRootNode.getPath());
+        assertTrue(!entries.isEmpty());
+        assertEquals(1, entries.size());
+
+        Entry entry = entries.iterator().next();
         for (String str : toMatch.keySet()) {
-            assertEquals("Path to match : " + str, toMatch.get(str).booleanValue(), ace.matches(str));
+            assertEquals("Path to match : " + str, toMatch.get(str).booleanValue(), entry.matches(str));
         }
     }
 }
\ No newline at end of file
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/ReadNodeTypeTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/ReadNodeTypeTest.java
new file mode 100644
index 0000000..3dc12fb
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/ReadNodeTypeTest.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.security.authorization.acl;
+
+import java.security.Principal;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import javax.jcr.AccessDeniedException;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.core.security.authorization.AbstractEvaluationTest;
+import org.apache.jackrabbit.core.security.authorization.AccessControlConstants;
+import org.apache.jackrabbit.test.NotExecutableException;
+
+public class ReadNodeTypeTest extends AbstractEvaluationTest {
+
+    private String path;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // create some nodes below the test root in order to apply ac-stuff
+        Node node = testRootNode.addNode(nodeName1, testNodeType);
+        node.addMixin(NodeType.MIX_LOCKABLE);
+        superuser.save();
+
+        path = node.getPath();
+    }
+
+    @Override
+    protected boolean isExecutable() {
+        return EvaluationUtil.isExecutable(acMgr);
+    }
+
+    @Override
+    protected JackrabbitAccessControlList getPolicy(AccessControlManager acM, String path, Principal principal) throws RepositoryException, AccessDeniedException, NotExecutableException {
+        return EvaluationUtil.getPolicy(acM, path, principal);
+    }
+
+    @Override
+    protected Map<String, Value> getRestrictions(Session s, String path) {
+        return Collections.emptyMap();
+    }
+
+    /**
+     * @see <a href="https://issues.apache.org/jira/browse/OAK-2441">OAK-2441</a>
+     */
+    public void testNodeGetPrimaryType() throws Exception {
+        Map<String, Value> rest = new HashMap<String, Value>(getRestrictions(superuser, path));
+        rest.put(AccessControlConstants.P_GLOB.toString(), vf.createValue("/jcr:*"));
+
+        withdrawPrivileges(path, privilegesFromName(Privilege.JCR_READ), rest);
+
+        Session testSession = getTestSession();
+        Node n = testSession.getNode(path);
+
+        assertFalse(testSession.propertyExists(path + '/' + JcrConstants.JCR_PRIMARYTYPE));
+        assertFalse(n.hasProperty(JcrConstants.JCR_PRIMARYTYPE));
+
+        NodeType primary = n.getPrimaryNodeType();
+    }
+
+    /**
+     * @see <a href="https://issues.apache.org/jira/browse/OAK-2441">OAK-2441</a>
+     */
+    public void testNodeGetMixinTypes() throws Exception {
+        Session testSession = getTestSession();
+        assertTrue(testSession.propertyExists(path + '/' + JcrConstants.JCR_MIXINTYPES));
+
+        Map<String, Value> rest = new HashMap<String, Value>(getRestrictions(superuser, path));
+        rest.put(AccessControlConstants.P_GLOB.toString(), vf.createValue("/jcr:*"));
+
+        withdrawPrivileges(path, privilegesFromName(Privilege.JCR_READ), rest);
+
+        int noMixins = superuser.getNode(path).getMixinNodeTypes().length;
+
+        Node n = testSession.getNode(path);
+        assertFalse(testSession.propertyExists(path + '/' + JcrConstants.JCR_MIXINTYPES));
+        assertFalse(n.hasProperty(JcrConstants.JCR_MIXINTYPES));
+
+        NodeType[] mixins = n.getMixinNodeTypes();
+        assertEquals(noMixins, mixins.length);
+    }
+
+    public void testNodeGetMixinTypesWithTransientModifications() throws Exception {
+        int noMixins = superuser.getNode(path).getMixinNodeTypes().length;
+
+        Node node = superuser.getNode(path);
+        node.addMixin(NodeType.MIX_CREATED);
+
+        NodeType[] mixins = node.getMixinNodeTypes();
+        assertEquals(noMixins+1, mixins.length);
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/ReadTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/ReadTest.java
index 7de9f92..dcd1873 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/ReadTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/ReadTest.java
@@ -16,14 +16,19 @@
  */
 package org.apache.jackrabbit.core.security.authorization.acl;
 
+import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.api.JackrabbitSession;
 import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
 import org.apache.jackrabbit.core.security.authorization.AbstractEvaluationTest;
 import org.apache.jackrabbit.core.security.authorization.AccessControlConstants;
 import org.apache.jackrabbit.test.NotExecutableException;
+import org.junit.Test;
 
 import javax.jcr.AccessDeniedException;
 import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.Value;
@@ -339,6 +344,146 @@ public class ReadTest extends AbstractEvaluationTest {
         assertFalse(testSession.propertyExists(propPath));
     }
 
+    /**
+     * @see <a href="https://issues.apache.org/jira/browse/OAK-2412">OAK-2412</a>
+     */
+    @Test
+    public void testEmptyGlobRestriction()throws Exception{
+        Node grandchild = superuser.getNode(childNPath).addNode("child");
+        String ccPath = grandchild.getPath();
+        superuser.save();
+
+        // first deny access to 'path' (read-access is granted in the test setup)
+        Privilege[] read = privilegesFromName(Privilege.JCR_READ);
+        withdrawPrivileges(path, read, Collections.EMPTY_MAP);
+
+        Session testSession = getTestSession();
+        assertFalse(testSession.nodeExists(path));
+        assertFalse(canGetNode(testSession, path));
+        assertFalse(testSession.nodeExists(childNPath));
+        assertFalse(canGetNode(testSession, childNPath));
+        assertFalse(testSession.nodeExists(ccPath));
+        assertFalse(canGetNode(testSession, ccPath));
+        assertFalse(testSession.propertyExists(childNPath + '/' + JcrConstants.JCR_PRIMARYTYPE));
+
+        Map<String, Value> emptyStringRestriction = new HashMap<String, Value>(getRestrictions(superuser, childNPath));
+        emptyStringRestriction.put(AccessControlConstants.P_GLOB.toString(), vf.createValue(""));
+
+        givePrivileges(childNPath, read, emptyStringRestriction);
+        assertFalse(testSession.nodeExists(path));
+        assertFalse(canGetNode(testSession, path));
+        assertTrue(testSession.nodeExists(childNPath));
+        assertTrue(canGetNode(testSession, childNPath));
+        assertFalse(testSession.nodeExists(ccPath));
+        assertFalse(canGetNode(testSession, ccPath));
+        assertFalse(testSession.propertyExists(childNPath + '/' + JcrConstants.JCR_PRIMARYTYPE));
+
+        givePrivileges(ccPath, read, Collections.EMPTY_MAP);
+        assertTrue(testSession.nodeExists(ccPath));
+        assertTrue(canGetNode(testSession, ccPath));
+        assertTrue(testSession.propertyExists(ccPath + '/' + JcrConstants.JCR_PRIMARYTYPE));
+    }
+
+    /**
+     * @see <a href="https://issues.apache.org/jira/browse/OAK-2412">OAK-2412</a>
+     */
+    @Test
+    public void testEmptyGlobRestriction2()throws Exception{
+        Node grandchild = superuser.getNode(childNPath).addNode("child");
+        String ccPath = grandchild.getPath();
+        superuser.save();
+
+        // first deny access to 'path' (read-access is granted in the test setup)
+        Privilege[] read = privilegesFromName(Privilege.JCR_READ);
+        withdrawPrivileges(path, read, Collections.EMPTY_MAP);
+
+        Session testSession = getTestSession();
+        assertFalse(testSession.nodeExists(path));
+        assertFalse(canGetNode(testSession, path));
+        assertFalse(testSession.nodeExists(childNPath));
+        assertFalse(canGetNode(testSession, childNPath));
+        assertFalse(testSession.nodeExists(ccPath));
+        assertFalse(canGetNode(testSession, ccPath));
+        assertFalse(testSession.propertyExists(childNPath + '/' + JcrConstants.JCR_PRIMARYTYPE));
+
+        Map<String, Value> emptyStringRestriction = new HashMap<String, Value>(getRestrictions(superuser, path));
+        emptyStringRestriction.put(AccessControlConstants.P_GLOB.toString(), vf.createValue(""));
+
+        givePrivileges(path, read, emptyStringRestriction);
+        assertTrue(testSession.nodeExists(path));
+        assertTrue(canGetNode(testSession, path));
+        assertFalse(testSession.nodeExists(childNPath));
+        assertFalse(canGetNode(testSession, childNPath));
+        assertFalse(testSession.nodeExists(ccPath));
+        assertFalse(canGetNode(testSession, ccPath));
+        assertFalse(testSession.propertyExists(childNPath + '/' + JcrConstants.JCR_PRIMARYTYPE));
+    }
+
+    /**
+     * @see <a href="https://issues.apache.org/jira/browse/OAK-2412">OAK-2412</a>
+     */
+    @Test
+    public void testEmptyGlobRestriction3()throws Exception{
+        Node child2 = superuser.getNode(path).addNode("child2");
+        String childNPath2 = child2.getPath();
+        superuser.save();
+
+        try {
+            Group group1 = getTestGroup();
+            Group group2 = getUserManager(superuser).createGroup("group2");
+            group2.addMember(testUser);
+            Group group3 = getUserManager(superuser).createGroup("group3");
+            superuser.save();
+
+            assertTrue(group1.isDeclaredMember(testUser));
+            assertTrue(group2.isDeclaredMember(testUser));
+            assertFalse(group3.isDeclaredMember(testUser));
+
+            Privilege[] read = privilegesFromName(Privilege.JCR_READ);
+
+            withdrawPrivileges(path, group1.getPrincipal(), read, Collections.EMPTY_MAP);
+            Map<String, Value> emptyStringRestriction = new HashMap<String, Value>(getRestrictions(superuser, path));
+            emptyStringRestriction.put(AccessControlConstants.P_GLOB.toString(), vf.createValue(""));
+            givePrivileges(path, group1.getPrincipal(), read, emptyStringRestriction);
+
+            withdrawPrivileges(childNPath, group2.getPrincipal(), read, Collections.EMPTY_MAP);
+            emptyStringRestriction = new HashMap<String, Value>(getRestrictions(superuser, childNPath));
+            emptyStringRestriction.put(AccessControlConstants.P_GLOB.toString(), vf.createValue(""));
+            givePrivileges(childNPath, group2.getPrincipal(), read, emptyStringRestriction);
+
+            withdrawPrivileges(childNPath2, group3.getPrincipal(), read, Collections.EMPTY_MAP);
+            emptyStringRestriction = new HashMap<String, Value>(getRestrictions(superuser, childNPath2));
+            emptyStringRestriction.put(AccessControlConstants.P_GLOB.toString(), vf.createValue(""));
+            givePrivileges(childNPath2, group3.getPrincipal(), read, emptyStringRestriction);
+
+            // NOTE: test-session is created here and is expected to reflect the
+            // group membership changes made above.
+            Session testSession = getTestSession();
+            assertTrue(testSession.nodeExists(path));
+            assertTrue(testSession.nodeExists(childNPath));
+            assertFalse(testSession.nodeExists(childNPath2));
+        } finally {
+            Authorizable g2 = getUserManager(superuser).getAuthorizable("group2");
+            if (g2 != null) {
+                g2.remove();
+            }
+            Authorizable g3 = getUserManager(superuser).getAuthorizable("group3");
+            if (g3 != null) {
+                g3.remove();
+            }
+            superuser.save();
+        }
+    }
+
+    private static boolean canGetNode(Session session, String nodePath) throws RepositoryException {
+        try {
+            session.getNode(nodePath);
+            return true;
+        } catch (PathNotFoundException e) {
+            return false;
+        }
+    }
+
     public void testRemoveMixin() throws Exception {
         Node n = superuser.getNode(path);
         
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/TestAll.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/TestAll.java
index ac96714..3d823cb 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/TestAll.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/TestAll.java
@@ -38,6 +38,7 @@ public class TestAll extends TestCase {
         TestSuite suite = new ConcurrentTestSuite("security.authorization.acl tests");
 
         suite.addTestSuite(ACLTemplateTest.class);
+        suite.addTestSuite(ACLTemplateEntryTest.class);
         suite.addTestSuite(EntryTest.class);
         suite.addTestSuite(EntryCollectorTest.class);
 
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/WriteTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/WriteTest.java
index a6ae45e..cd31be2 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/WriteTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/authorization/acl/WriteTest.java
@@ -26,6 +26,7 @@ import org.apache.jackrabbit.core.security.authorization.PrivilegeRegistry;
 import org.apache.jackrabbit.core.security.principal.EveryonePrincipal;
 import org.apache.jackrabbit.core.security.TestPrincipal;
 import org.apache.jackrabbit.test.NotExecutableException;
+import org.apache.jackrabbit.util.Text;
 
 import javax.jcr.AccessDeniedException;
 import javax.jcr.Node;
@@ -530,4 +531,26 @@ public class WriteTest extends AbstractWriteTest {
             superuser.save();
         }
     }
+
+    public void testReorderPolicyNode() throws RepositoryException, NotExecutableException {
+        Session testSession = getTestSession();
+        Node n = testSession.getNode(path);
+        try {
+            if (!n.getPrimaryNodeType().hasOrderableChildNodes()) {
+                throw new NotExecutableException("Reordering child nodes is not supported..");
+            }
+
+            n.orderBefore(Text.getName(childNPath), Text.getName(childNPath2));
+            testSession.save();
+            fail("test session must not be allowed to reorder nodes.");
+        } catch (AccessDeniedException e) {
+            // success.
+        }
+
+        // grant all privileges
+        givePrivileges(path, privilegesFromNames(new String[] {Privilege.JCR_ALL}), getRestrictions(superuser, path));
+
+        n.orderBefore("rep:policy", Text.getName(childNPath2));
+        testSession.save();
+    }
 }
\ No newline at end of file
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/principal/PrincipalManagerTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/principal/PrincipalManagerTest.java
new file mode 100644
index 0000000..5407eff
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/principal/PrincipalManagerTest.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.security.principal;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.Properties;
+
+import org.apache.jackrabbit.api.security.principal.JackrabbitPrincipal;
+import org.apache.jackrabbit.api.security.principal.PrincipalIterator;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.jackrabbit.test.NotExecutableException;
+import sun.security.acl.GroupImpl;
+
+
+/**
+ * <code>PrincipalManagerTest</code>...
+ */
+public class PrincipalManagerTest extends AbstractJCRTest {
+
+    private static final String TESTGROUP_NAME = "org.apache.jackrabbit.core.security.principal.PrincipalManagerTest.testgroup";
+    private static final Group TESTGROUP = new GroupImpl(TESTGROUP_NAME);
+
+    private static class CustomPrincipalProvider extends AbstractPrincipalProvider {
+
+        protected Principal providePrincipal(String principalName) {
+            return TESTGROUP_NAME.equals(principalName) ? TESTGROUP : null;
+        }
+
+        public PrincipalIterator findPrincipals(String simpleFilter) {
+            throw new UnsupportedOperationException();
+        }
+
+        public PrincipalIterator findPrincipals(String simpleFilter, int searchType) {
+            throw new UnsupportedOperationException();
+        }
+
+        public PrincipalIterator getPrincipals(int searchType) {
+            throw new UnsupportedOperationException();
+        }
+
+        public PrincipalIterator getGroupMembership(Principal principal) {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean canReadPrincipal(Session session, Principal principalToRead) {
+            return true;
+        }
+    }
+
+    /**
+     * Test if a group which is not item based will be wrapped by a JackrabbitPrincipal implementation.
+     * @throws NotExecutableException
+     * @throws RepositoryException
+     */
+    public void testJackrabbitPrincipal() throws NotExecutableException, RepositoryException {
+
+        final PrincipalProvider testProvider = new CustomPrincipalProvider();
+        testProvider.init(new Properties());
+        PrincipalManagerImpl principalManager = new PrincipalManagerImpl(superuser, new PrincipalProvider[] { testProvider });
+        Principal principalFromManager = principalManager.getPrincipal(TESTGROUP_NAME);
+        assertTrue(principalFromManager instanceof JackrabbitPrincipal);
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/principal/TestAll.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/principal/TestAll.java
index 563bc12..8420fcb 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/principal/TestAll.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/principal/TestAll.java
@@ -37,7 +37,8 @@ public class TestAll extends TestCase {
 
         suite.addTestSuite(AbstractPrincipalProviderTest.class);
         suite.addTestSuite(EveryonePrincipalTest.class);
-        
+        suite.addTestSuite(PrincipalManagerTest.class);
+
         return suite;
     }
 }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/simple/SimpleSecurityManagerTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/simple/SimpleSecurityManagerTest.java
new file mode 100644
index 0000000..53d730f
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/simple/SimpleSecurityManagerTest.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.security.simple;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import junit.framework.TestCase;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.junit.Test;
+
+public class SimpleSecurityManagerTest extends TestCase {
+
+    private Repository repository;
+
+    @Override
+    public void setUp() throws RepositoryException {
+        String file = "src/test/resources/org/apache/jackrabbit/core/security/simple/simple_repository.xml";
+        RepositoryConfig config = RepositoryConfig.create(file, "target/simple_repository");
+        repository = RepositoryImpl.create(config);
+    }
+
+    /**
+     * @see <a href="https://issues.apache.org/jira/browse/JCR-3697">JCR-3697</a>
+     */
+    @Test
+    public void testRemove() throws RepositoryException {
+        Session s = repository.login(new SimpleCredentials("admin", "admin".toCharArray()));
+        Node n = s.getRootNode().addNode(("a"));
+        s.save();
+
+        n.remove();
+        s.save();
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/simple/TestAll.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/simple/TestAll.java
new file mode 100644
index 0000000..1c3fcd2
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/simple/TestAll.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.security.simple;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+public class TestAll extends TestCase {
+
+    public static Test suite() {
+        TestSuite suite = new TestSuite("core.security.simple tests");
+
+        suite.addTestSuite(SimpleSecurityManagerTest.class);
+
+        return suite;
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/AdministratorTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/AdministratorTest.java
index 621891b..b26ac51 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/AdministratorTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/AdministratorTest.java
@@ -16,21 +16,21 @@
  */
 package org.apache.jackrabbit.core.security.user;
 
+import java.util.Properties;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
 import org.apache.jackrabbit.api.security.user.AbstractUserTest;
 import org.apache.jackrabbit.api.security.user.Authorizable;
-import org.apache.jackrabbit.api.security.user.UserManager;
 import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
 import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
-import org.apache.jackrabbit.test.NotExecutableException;
 import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import java.util.Properties;
+import org.apache.jackrabbit.test.NotExecutableException;
 
 /**
  * <code>AdministratorTest</code>...
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/AuthorizableActionTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/AuthorizableActionTest.java
index 62d2f81..f61095f 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/AuthorizableActionTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/AuthorizableActionTest.java
@@ -230,7 +230,51 @@ public class AuthorizableActionTest extends AbstractUserTest {
         }
     }
 
+    public void testPasswordValidationActionIgnoresHashedPwStringOnCreate() throws Exception {
+        User u = null;
+
+        try {
+            PasswordValidationAction pwAction = new PasswordValidationAction();
+            pwAction.setConstraint("^.*(?=.{8,})(?=.*[a-z])(?=.*[A-Z]).*");
+            setActions(pwAction);
+
+            String uid = getTestPrincipal().getName();
+            String hashed = PasswordUtility.buildPasswordHash("DWkej32H");
+            u = impl.createUser(uid, hashed);
+
+        } finally {
+            if (u != null) {
+                u.remove();
+            }
+            save(superuser);
+        }
+    }
+
+    public void testPasswordValidationActionOnChange() throws Exception {
+        User u = null;
+
+        try {
+            String uid = getTestPrincipal().getName();
+            u = impl.createUser(uid, buildPassword(uid));
+
+            PasswordValidationAction pwAction = new PasswordValidationAction();
+            pwAction.setConstraint("abc");
+            setActions(pwAction);
+
+            String hashed = PasswordUtility.buildPasswordHash("abc");
+            u.changePassword(hashed);
 
+            fail("Password change must always enforce password validation.");
+
+        } catch (ConstraintViolationException e) {
+            // success
+        } finally {
+            if (u != null) {
+                u.remove();
+            }
+            save(superuser);
+        }
+    }
 
     //--------------------------------------------------------------------------
     private class TestAction extends AbstractAuthorizableAction {
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/AuthorizableImplTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/AuthorizableImplTest.java
index 70d726c..91b345a 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/AuthorizableImplTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/AuthorizableImplTest.java
@@ -28,8 +28,10 @@ import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
 import org.apache.jackrabbit.test.NotExecutableException;
 import org.apache.jackrabbit.value.StringValue;
 
+import javax.jcr.Node;
 import javax.jcr.Property;
 import javax.jcr.PropertyIterator;
+import javax.jcr.RangeIterator;
 import javax.jcr.RepositoryException;
 import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.Value;
@@ -41,6 +43,7 @@ import java.util.Iterator;
 import java.util.HashSet;
 import java.util.Set;
 import java.security.Principal;
+import java.util.UUID;
 
 /**
  * <code>AuthorizableImplTest</code>...
@@ -59,8 +62,9 @@ public class AuthorizableImplTest extends AbstractUserTest {
             protectedUserProps.add(resolver.getJCRName(UserConstants.P_PASSWORD));
             protectedUserProps.add(resolver.getJCRName(UserConstants.P_IMPERSONATORS));
             protectedUserProps.add(resolver.getJCRName(UserConstants.P_PRINCIPAL_NAME));
+            protectedUserProps.add(resolver.getJCRName(UserConstants.P_DISABLED));
 
-            protectedUserProps.add(resolver.getJCRName(UserConstants.P_MEMBERS));
+            protectedGroupProps.add(resolver.getJCRName(UserConstants.P_MEMBERS));
             protectedGroupProps.add(resolver.getJCRName(UserConstants.P_PRINCIPAL_NAME));
         } else {
             throw new NotExecutableException();
@@ -170,6 +174,42 @@ public class AuthorizableImplTest extends AbstractUserTest {
         }
     }
 
+    public void testMemberOfRangeIterator() throws NotExecutableException, RepositoryException {
+        Authorizable auth = null;
+        Group group = null;
+
+        try {
+            auth = userMgr.createUser(getTestPrincipal().getName(), "pw");
+            group = userMgr.createGroup(getTestPrincipal());
+            save(superuser);
+
+            Iterator<Group>groups = auth.declaredMemberOf();
+            assertTrue(groups instanceof RangeIterator);
+            assertEquals(0, ((RangeIterator) groups).getSize());
+            groups = auth.memberOf();
+            assertTrue(groups instanceof RangeIterator);
+            assertEquals(0, ((RangeIterator) groups).getSize());
+
+            group.addMember(auth);
+            groups = auth.declaredMemberOf();
+            assertTrue(groups instanceof RangeIterator);
+            assertEquals(1, ((RangeIterator) groups).getSize());
+
+            groups = auth.memberOf();
+            assertTrue(groups instanceof RangeIterator);
+            assertEquals(1, ((RangeIterator) groups).getSize());
+
+        } finally {
+            if (auth != null) {
+                auth.remove();
+            }
+            if (group != null) {
+                group.remove();
+            }
+            save(superuser);
+        }
+    }
+
     public void testSetSpecialPropertiesDirectly() throws NotExecutableException, RepositoryException {
         AuthorizableImpl user = (AuthorizableImpl) getTestUser(superuser);
         NodeImpl n = user.getNode();
@@ -394,4 +434,43 @@ public class AuthorizableImplTest extends AbstractUserTest {
         }
 
     }
+
+    /**
+     * this is a very specialized test for JCR-3654
+     * @throws Exception
+     */
+    public void testMembershipCacheTraversal() throws Exception {
+        UserManager uMgr = getUserManager(superuser);
+        //uMgr.autoSave(true);
+        User u = uMgr.createUser("any_principal" + UUID.randomUUID(), "foobar");
+
+        // create group with mixin properties
+        GroupImpl gr = (GroupImpl) uMgr.createGroup("any_group" + UUID.randomUUID());
+        for (int i=0; i<100; i++) {
+            User testUser = uMgr.createUser("any_principal" + UUID.randomUUID(), "foobar");
+            gr.addMember(testUser);
+        }
+
+        gr.addMember(u);
+        NodeImpl n = gr.getNode();
+        n.addMixin("mix:title");
+        superuser.save();
+
+        // removing the authorizable node forces a traversal to collect the memberships
+        //u = (User) uMgr.getAuthorizable(u.getID());
+        //superuser.getNode(u.getPath()).remove();
+        u.remove();
+        Iterator<Group> grp = u.declaredMemberOf();
+        assertTrue("User need to be member of group", grp.hasNext());
+        Group result = grp.next();
+        assertEquals("User needs to be member of group", gr.getID(), result.getID());
+
+        Iterator<Authorizable> auths = gr.getDeclaredMembers();
+        int i = 0;
+        while (auths.hasNext()) {
+            auths.next();
+            i++;
+        }
+        assertEquals("Group needs to have 100 members", 100, i);
+    }
 }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/DefaultPrincipalProviderTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/DefaultPrincipalProviderTest.java
index c5eb325..1a12d50 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/DefaultPrincipalProviderTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/DefaultPrincipalProviderTest.java
@@ -16,6 +16,12 @@
  */
 package org.apache.jackrabbit.core.security.user;
 
+import java.security.Principal;
+import java.util.Properties;
+import java.util.Set;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
 import org.apache.jackrabbit.api.security.principal.PrincipalIterator;
 import org.apache.jackrabbit.api.security.user.AbstractUserTest;
 import org.apache.jackrabbit.api.security.user.Authorizable;
@@ -27,12 +33,6 @@ import org.apache.jackrabbit.core.security.principal.EveryonePrincipal;
 import org.apache.jackrabbit.core.security.principal.PrincipalProvider;
 import org.apache.jackrabbit.test.NotExecutableException;
 
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import java.security.Principal;
-import java.util.Properties;
-import java.util.Set;
-
 /**
  * <code>DefaultPrincipalProviderTest</code>...
  */
@@ -227,4 +227,37 @@ public class DefaultPrincipalProviderTest extends AbstractUserTest {
             }
         }
     }
+
+    /**
+     * Test for: Principal assiocated with Group does not update members
+     * @see <a href=https://issues.apache.org/jira/browse/JCR-3552>JCR-3552</a>
+     */
+    public void testGroupMembership() throws Exception {
+        Group g = null;
+        User u = null;
+        Principal up = getTestPrincipal();
+        try {
+            // create a group and user, add the user to the group and assert membership
+            g = userMgr.createGroup(getTestPrincipal());
+            u = userMgr.createUser(up.getName(), buildPassword(up));
+            save(superuser);
+            g.addMember(u);
+            save(superuser);
+
+            Principal groupPrincipal = principalProvider.getPrincipal(g.getPrincipal().getName());
+            assertTrue(groupPrincipal instanceof java.security.acl.Group);
+            assertTrue(((java.security.acl.Group) groupPrincipal).isMember(u.getPrincipal()));
+
+            // remove the user from the group and assert the user is no longer a member of the group
+            g.removeMember(u);
+            save(superuser);
+
+            groupPrincipal = principalProvider.getPrincipal(g.getPrincipal().getName());
+            assertFalse(((java.security.acl.Group) groupPrincipal).isMember(u.getPrincipal()));
+        } finally {
+            if (null != g) { g.remove(); }
+            if (null != u) { u.remove(); }
+            save(superuser);
+        }
+    }
 }
\ No newline at end of file
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/MembershipCacheTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/MembershipCacheTest.java
new file mode 100644
index 0000000..c66b752
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/MembershipCacheTest.java
@@ -0,0 +1,235 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.security.user;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.apache.jackrabbit.test.JUnitTest;
+
+/**
+ * Performance test for JCR-3658.
+ */
+public class MembershipCacheTest extends JUnitTest {
+
+    private static final String TEST_USER_PREFIX = "MembershipCacheTestUser-";
+    private static final String REPO_HOME = new File("target",
+            MembershipCacheTest.class.getSimpleName()).getPath();
+    private static final int NUM_USERS = 100;
+    private static final int NUM_GROUPS = 8;
+    private static final int NUM_READERS = 8;
+    private RepositoryImpl repo;
+    private JackrabbitSession session;
+    private UserManager userMgr;
+    private MembershipCache cache;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        FileUtils.deleteDirectory(new File(REPO_HOME));
+        RepositoryConfig config = RepositoryConfig.create(
+                getClass().getResourceAsStream("repository.xml"), REPO_HOME);
+        repo = RepositoryImpl.create(config);
+        session = createSession();
+        userMgr = session.getUserManager();
+        cache = ((UserManagerImpl) userMgr).getMembershipCache();
+        boolean autoSave = userMgr.isAutoSave();
+        userMgr.autoSave(false);
+        // create test users and groups
+        List<User> users = new ArrayList<User>();
+        for (int i = 0; i < NUM_USERS; i++) {
+            users.add(userMgr.createUser(TEST_USER_PREFIX + i, "secret"));
+        }
+        for (int i = 0; i < NUM_GROUPS; i++) {
+            Group g = userMgr.createGroup("MembershipCacheTestGroup-" + i);
+            for (User u : users) {
+                g.addMember(u);
+            }
+        }
+        session.save();
+        userMgr.autoSave(autoSave);
+        logger.info("Initial cache size: " + cache.getSize());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        boolean autoSave = userMgr.isAutoSave();
+        userMgr.autoSave(false);
+        for (int i = 0; i < NUM_USERS; i++) {
+            userMgr.getAuthorizable(TEST_USER_PREFIX + i).remove();
+        }
+        for (int i = 0; i < NUM_GROUPS; i++) {
+            userMgr.getAuthorizable("MembershipCacheTestGroup-" + i).remove();
+        }
+        session.save();
+        userMgr.autoSave(autoSave);
+        userMgr = null;
+        cache = null;
+        session.logout();
+        repo.shutdown();
+        repo = null;
+        FileUtils.deleteDirectory(new File(REPO_HOME));
+        super.tearDown();
+    }
+
+    public void testConcurrency() throws Exception {
+        Stats stats = new Stats();
+        List<Exception> exceptions = Collections.synchronizedList(new ArrayList<Exception>());
+        List<Reader> readers = new ArrayList<Reader>();
+        for (int i = 0; i < NUM_READERS; i++) {
+            Reader r = new Reader(createSession(), stats, exceptions);
+            r.addUser(TEST_USER_PREFIX + 0);
+            readers.add(r);
+        }
+        Node test = session.getRootNode().addNode("test", "nt:unstructured");
+        session.save();
+        for (Reader r : readers) {
+            r.start();
+        }
+        for (int i = 1; i < NUM_USERS; i++) {
+            test.addNode("node-" + i);
+            session.save();
+            for (Reader r : readers) {
+                r.addUser(TEST_USER_PREFIX + i);
+            }
+        }
+        for (Reader r : readers) {
+            r.join();
+        }
+        test.remove();
+        session.save();
+        System.out.println(stats);
+        for (Exception e : exceptions) {
+            throw e;
+        }
+    }
+
+    public void testRun75() throws Exception {
+        for (int i = 0; i < 75; i++) {
+            testConcurrency();
+            cache.clear();
+        }
+    }
+
+    private JackrabbitSession createSession() throws RepositoryException {
+        return (JackrabbitSession) repo.login(
+                new SimpleCredentials("admin", "admin".toCharArray()));
+    }
+
+    private static final class Reader extends Thread {
+
+        private final JackrabbitSession session;
+        private final UserManager userMgr;
+        private final Stats stats;
+        private final List<Object> knownUsers = new ArrayList<Object>();
+        private final Random random = new Random();
+        private final List<Exception> exceptions;
+
+        public Reader(JackrabbitSession s,
+                      Stats stats,
+                      List<Exception> exceptions)
+                throws RepositoryException {
+            this.session = s;
+            this.userMgr = s.getUserManager();
+            this.stats = stats;
+            this.exceptions = exceptions;
+        }
+
+        void addUser(String user) {
+            synchronized (knownUsers) {
+                knownUsers.add(user);
+            }
+        }
+
+        public void run() {
+            try {
+                while (knownUsers.size() < NUM_USERS) {
+                    Object idOrUser;
+                    int idx;
+                    synchronized (knownUsers) {
+                        idx = random.nextInt(knownUsers.size());
+                        idOrUser = knownUsers.get(idx);
+                    }
+                    User user;
+                    if (idOrUser instanceof String) {
+                        user = (User) userMgr.getAuthorizable((String) idOrUser);
+                        synchronized (knownUsers) {
+                            knownUsers.set(idx, user);
+                        }
+                    } else {
+                        user = (User) idOrUser;
+                    }
+                    long time = System.nanoTime();
+                    user.memberOf();
+                    stats.logTime(System.nanoTime() - time);
+                }
+            } catch (RepositoryException e) {
+                exceptions.add(e);
+            } finally {
+                session.logout();
+            }
+        }
+    }
+
+    private static final class Stats {
+
+        private AtomicLong[] buckets = new AtomicLong[20];
+
+        public Stats() {
+            for (int i = 0; i < buckets.length; i++) {
+                buckets[i] = new AtomicLong();
+            }
+        }
+
+        void logTime(long nanos) {
+            if (nanos == 0) {
+                buckets[0].incrementAndGet();
+            } else {
+                buckets[(int) Math.log10(nanos)].incrementAndGet();
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            String separator = "";
+            for (AtomicLong bucket : buckets) {
+                sb.append(separator);
+                sb.append(bucket.get());
+                separator = ",";
+            }
+            return sb.toString();
+        }
+    }
+
+
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/NodeCreationTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/NodeCreationTest.java
index 18d0a63..8773eb6 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/NodeCreationTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/NodeCreationTest.java
@@ -38,7 +38,7 @@ import java.util.Map;
 import java.util.Properties;
 
 /**
- * <code>IdResolverTest</code>...
+ * <code>NodeCreationTest</code>...
  */
 public class NodeCreationTest extends AbstractUserTest {
 
@@ -140,7 +140,7 @@ public class NodeCreationTest extends AbstractUserTest {
         m.put("zh",     "/z/zh/zh");
         m.put("zHzh",   "/z/zH/zHzh");
         m.put("z_Hz",   "/z/z_/z_Hz");
-        m.put("z�rich", "/z/z�/z�rich");
+        m.put("z\u00cfrich", "/z/z\u00cf/z\u00cfrich");
 
         for (String uid : m.keySet()) {
             u = (UserImpl) uMgr.createUser(uid, uid);
@@ -171,7 +171,7 @@ public class NodeCreationTest extends AbstractUserTest {
         m.put("zH",     "/z/zH/zHH/zH");
         m.put("zHzh",   "/z/zH/zHz/zHzh");
         m.put("z_Hz",   "/z/z_/z_H/z_Hz");
-        m.put("z�rich", "/z/z�/z�r/z�rich");
+        m.put("z\u00cfrich", "/z/z\u00cf/z\u00cfr/z\u00cfrich");
 
         for (String uid : m.keySet()) {
             u = (UserImpl) uMgr.createUser(uid, uid);
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/PasswordUtilityTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/PasswordUtilityTest.java
new file mode 100644
index 0000000..fed9c5b
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/PasswordUtilityTest.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.core.security.user;
+
+import org.apache.jackrabbit.core.security.SecurityConstants;
+import org.apache.jackrabbit.test.JUnitTest;
+
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <code>PasswordUtilityTest</code>...
+ */
+public class PasswordUtilityTest extends JUnitTest {
+
+    private static List<String> PLAIN_PWDS = new ArrayList<String>();
+    static {
+        PLAIN_PWDS.add("pw");
+        PLAIN_PWDS.add("PassWord123");
+        PLAIN_PWDS.add("_");
+        PLAIN_PWDS.add("{invalidAlgo}");
+        PLAIN_PWDS.add("{invalidAlgo}Password");
+        PLAIN_PWDS.add("{SHA-256}");
+        PLAIN_PWDS.add("pw{SHA-256}");
+        PLAIN_PWDS.add("p{SHA-256}w");
+        PLAIN_PWDS.add("");
+    }
+
+    private static Map<String, String> HASHED_PWDS = new HashMap<String, String>();
+    static {
+        for (String pw : PLAIN_PWDS) {
+            try {
+                HASHED_PWDS.put(pw, PasswordUtility.buildPasswordHash(pw));
+            } catch (Exception e) {
+                // should not get here
+            }
+        }
+    }
+
+    public void testBuildPasswordHash() throws Exception {
+        for (String pw : PLAIN_PWDS) {
+            String pwHash = PasswordUtility.buildPasswordHash(pw);
+            assertFalse(pw.equals(pwHash));
+        }
+
+        List<Integer[]> l = new ArrayList<Integer[]>();
+        l.add(new Integer[] {0, 1000});
+        l.add(new Integer[] {1, 10});
+        l.add(new Integer[] {8, 50});
+        l.add(new Integer[] {10, 5});
+        l.add(new Integer[] {-1, -1});
+        for (Integer[] params : l) {
+            for (String pw : PLAIN_PWDS) {
+                int saltsize = params[0];
+                int iterations = params[1];
+
+                String pwHash = PasswordUtility.buildPasswordHash(pw, PasswordUtility.DEFAULT_ALGORITHM, saltsize, iterations);
+                assertFalse(pw.equals(pwHash));
+            }
+        }
+    }
+
+    public void testBuildPasswordHashInvalidAlgorithm() throws Exception {
+        List<String> invalidAlgorithms = new ArrayList<String>();
+        invalidAlgorithms.add("");
+        invalidAlgorithms.add("+");
+        invalidAlgorithms.add("invalid");
+
+        for (String invalid : invalidAlgorithms) {
+            try {
+                String pwHash = PasswordUtility.buildPasswordHash("pw", invalid, PasswordUtility.DEFAULT_SALT_SIZE, PasswordUtility.DEFAULT_ITERATIONS);
+                fail("Invalid algorithm " + invalid);
+            } catch (NoSuchAlgorithmException e) {
+                // success
+            }
+        }
+
+    }
+
+    public void testIsPlainTextPassword() throws Exception {
+        for (String pw : PLAIN_PWDS) {
+            assertTrue(pw + " should be plain text.", PasswordUtility.isPlainTextPassword(pw));
+        }
+    }
+
+    public void testIsPlainTextForNull() throws Exception {
+        assertTrue(PasswordUtility.isPlainTextPassword(null));
+    }
+
+    public void testIsPlainTextForPwHash() throws Exception {
+        for (String pwHash : HASHED_PWDS.values()) {
+            assertFalse(pwHash + " should not be plain text.", PasswordUtility.isPlainTextPassword(pwHash));
+        }
+    }
+
+    public void testIsSame() throws Exception {
+        for (String pw : HASHED_PWDS.keySet()) {
+            String pwHash = HASHED_PWDS.get(pw);
+            assertTrue("Not the same " + pw + ", " + pwHash, PasswordUtility.isSame(pwHash, pw));
+        }
+
+        String pw = "password";
+        String pwHash = PasswordUtility.buildPasswordHash(pw, SecurityConstants.DEFAULT_DIGEST, 4, 50);
+        assertTrue("Not the same '" + pw + "', " + pwHash, PasswordUtility.isSame(pwHash, pw));
+
+        pwHash = PasswordUtility.buildPasswordHash(pw, "md5", 0, 5);
+        assertTrue("Not the same '" + pw + "', " + pwHash, PasswordUtility.isSame(pwHash, pw));
+
+        pwHash = PasswordUtility.buildPasswordHash(pw, "md5", -1, -1);
+        assertTrue("Not the same '" + pw + "', " + pwHash, PasswordUtility.isSame(pwHash, pw));
+    }
+
+    public void testIsNotSame() throws Exception {
+        String previous = null;
+        for (String pw : HASHED_PWDS.keySet()) {
+            String pwHash = HASHED_PWDS.get(pw);
+            assertFalse(pw, PasswordUtility.isSame(pw, pw));
+            assertFalse(pwHash, PasswordUtility.isSame(pwHash, pwHash));
+            if (previous != null) {
+                assertFalse(previous, PasswordUtility.isSame(pwHash, previous));
+            }
+            previous = pw;
+        }
+    }
+
+    public void testExtractAlgorithmFromPlainPw() throws Exception {
+        for (String pw : PLAIN_PWDS) {
+            assertNull(pw + " is no pw-hash -> no algorithm expected.", PasswordUtility.extractAlgorithm(pw));
+        }
+    }
+
+    public void testExtractAlgorithmFromNull() throws Exception {
+        assertNull("null pw -> no algorithm expected.", PasswordUtility.extractAlgorithm(null));
+    }
+
+    public void testExtractAlgorithmFromPwHash() throws Exception {
+        for (String pwHash : HASHED_PWDS.values()) {
+            String algorithm = PasswordUtility.extractAlgorithm(pwHash);
+            assertNotNull(pwHash + " is pw-hash -> algorithm expected.", algorithm);
+            assertEquals("Wrong algorithm extracted from " + pwHash, PasswordUtility.DEFAULT_ALGORITHM, algorithm);
+        }
+
+        String pwHash = PasswordUtility.buildPasswordHash("pw", SecurityConstants.DEFAULT_DIGEST, 4, 50);
+        assertEquals(SecurityConstants.DEFAULT_DIGEST, PasswordUtility.extractAlgorithm(pwHash));
+
+        pwHash = PasswordUtility.buildPasswordHash("pw", "md5", 0, 5);
+        assertEquals("md5", PasswordUtility.extractAlgorithm(pwHash));
+
+        pwHash = PasswordUtility.buildPasswordHash("pw", "md5", -1, -1);
+        assertEquals("md5", PasswordUtility.extractAlgorithm(pwHash));
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/TestAll.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/TestAll.java
index 5e86baf..f1dc824 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/TestAll.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/TestAll.java
@@ -54,6 +54,7 @@ public class TestAll extends TestCase {
         suite.addTestSuite(UserAccessControlProviderTest.class);
         suite.addTestSuite(DefaultPrincipalProviderTest.class);        
 
+        suite.addTestSuite(PasswordUtilityTest.class);
         return suite;
     }
 }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserImplTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserImplTest.java
index 627694e..77b6f34 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserImplTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserImplTest.java
@@ -142,9 +142,11 @@ public class UserImplTest extends AbstractUserTest {
         // plain text passwords
         pwds.put("abc", "abc");
         pwds.put("{a}password", "{a}password");
-        // passwords already in hashed format.
-        pwds.put(sha1Hash, "abc");
-        pwds.put(md5Hash, "abc");
+        // passwords with hash-like char-sequence -> must still be hashed.
+        pwds.put(sha1Hash, sha1Hash);
+        pwds.put(md5Hash, md5Hash);
+        pwds.put("{"+SecurityConstants.DEFAULT_DIGEST+"}any", "{"+SecurityConstants.DEFAULT_DIGEST+"}any");
+        pwds.put("{"+SecurityConstants.DEFAULT_DIGEST+"}", "{"+SecurityConstants.DEFAULT_DIGEST+"}");
 
         for (String pw : pwds.keySet()) {
             u.changePassword(pw);
@@ -159,11 +161,9 @@ public class UserImplTest extends AbstractUserTest {
         // valid passwords, non-matching plain text
         Map<String, String>noMatch = new HashMap<String, String>();
         noMatch.put("{"+SecurityConstants.DEFAULT_DIGEST+"}", "");
-        noMatch.put("{"+SecurityConstants.DEFAULT_DIGEST+"}", "{"+SecurityConstants.DEFAULT_DIGEST+"}");
         noMatch.put("{"+SecurityConstants.DEFAULT_DIGEST+"}any", "any");
-        noMatch.put("{"+SecurityConstants.DEFAULT_DIGEST+"}any", "{"+SecurityConstants.DEFAULT_DIGEST+"}any");
-        noMatch.put(sha1Hash, sha1Hash);
-        noMatch.put(md5Hash, md5Hash);
+        noMatch.put(sha1Hash, "abc");
+        noMatch.put(md5Hash, "abc");
 
         for (String pw : noMatch.keySet()) {
             u.changePassword(pw);
@@ -172,10 +172,14 @@ public class UserImplTest extends AbstractUserTest {
             SimpleCredentials sc = new SimpleCredentials(u.getID(), plain.toCharArray());
             CryptedSimpleCredentials cc = (CryptedSimpleCredentials) u.getCredentials();
 
-            assertFalse(cc.matches(sc));
+            assertFalse(pw, cc.matches(sc));
         }
+    }
+
+    public void testChangePasswordNull() throws RepositoryException {
+        User u = (User) userMgr.getAuthorizable(uID);
 
-        // invalid pw string
+        // invalid 'null' pw string
         try {
             u.changePassword(null);
             fail("invalid pw null");
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserImporterTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserImporterTest.java
index ab449bc..6403c0f 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserImporterTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserImporterTest.java
@@ -33,6 +33,8 @@ import org.apache.jackrabbit.core.config.ImportConfig;
 import org.apache.jackrabbit.core.security.SecurityConstants;
 import org.apache.jackrabbit.core.security.principal.PrincipalImpl;
 import org.apache.jackrabbit.core.security.user.UserImporter.ImportBehavior;
+import org.apache.jackrabbit.core.security.user.action.AccessControlAction;
+import org.apache.jackrabbit.core.security.user.action.AuthorizableAction;
 import org.apache.jackrabbit.core.util.ReferenceChangeTracker;
 import org.apache.jackrabbit.core.xml.ImportHandler;
 import org.apache.jackrabbit.core.xml.ProtectedNodeImporter;
@@ -69,7 +71,11 @@ import javax.jcr.lock.LockException;
 import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.nodetype.NoSuchNodeTypeException;
 import javax.jcr.retention.RetentionManager;
+import javax.jcr.security.AccessControlEntry;
+import javax.jcr.security.AccessControlList;
 import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.Privilege;
 import javax.jcr.version.VersionException;
 import javax.security.auth.Subject;
 import java.io.ByteArrayInputStream;
@@ -128,6 +134,7 @@ public class UserImporterTest extends AbstractJCRTest {
         }
         sImpl.save();
 
+        // make sure the target node for group-import exists
         Authorizable administrators = umgr.getAuthorizable(SecurityConstants.ADMINISTRATORS_NAME);
         if (administrators == null) {
             groupIdToRemove = umgr.createGroup(new PrincipalImpl(SecurityConstants.ADMINISTRATORS_NAME)).getID();
@@ -655,8 +662,23 @@ public class UserImporterTest extends AbstractJCRTest {
     }
 
     public void testImportNewMembers() throws IOException, RepositoryException, SAXException, NotExecutableException {
-        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><sv:node sv:name=\"gFolder\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
-                "<sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:AuthorizableFolder</sv:value></sv:property><sv:node sv:name=\"g\"><sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:Group</sv:value></sv:property><sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>b2f5ff47-4366-31b6-a533-d8dc3614845d</sv:value></sv:property><sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>g</sv:value></sv:property></sv:node><sv [...]
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
+                "<sv:node sv:name=\"gFolder\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
+                "<sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">" +
+                "   <sv:value>rep:AuthorizableFolder</sv:value>" +
+                "</sv:property>" +
+                "<sv:node sv:name=\"g\">" +
+                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:Group</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>b2f5ff47-4366-31b6-a533-d8dc3614845d</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>g</sv:value></sv:property>" +
+                "</sv:node>" +
+                "<sv:node sv:name=\"g1\">" +
+                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:Group</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>0120a4f9-196a-3f9e-b9f5-23f31f914da7</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>g1</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:members\" sv:type=\"WeakReference\"><sv:value>b2f5ff47-4366-31b6-a533-d8dc3614845d</sv:value></sv:property>" +
+                "</sv:node>" +
+                "</sv:node>";
 
         NodeImpl target = (NodeImpl) sImpl.getNode(umgr.getGroupsPath());
         try {
@@ -838,7 +860,7 @@ public class UserImporterTest extends AbstractJCRTest {
     }
 
     public void testImportNonExistingMemberBestEffort() throws IOException, RepositoryException, SAXException, NotExecutableException {
-        if (umgr.getGroupMembershipSplitSize() > 0) {
+        if (umgr.hasMemberSplitSize()) {
             throw new NotExecutableException();
         }
 
@@ -892,7 +914,7 @@ public class UserImporterTest extends AbstractJCRTest {
 
         String g1Id = "0120a4f9-196a-3f9e-b9f5-23f31f914da7";
         String nonExistingId = "b2f5ff47-4366-31b6-a533-d8dc3614845d"; // groupId of 'g' group.
-        if (umgr.getAuthorizable("g") != null || umgr.getGroupMembershipSplitSize() > 0) {
+        if (umgr.getAuthorizable("g") != null || umgr.hasMemberSplitSize()) {
             throw new NotExecutableException();
         }
 
@@ -1326,6 +1348,165 @@ public class UserImporterTest extends AbstractJCRTest {
         }
     }
 
+    public void testActionExecutionForUser() throws Exception {
+        TestAction testAction = new TestAction();
+
+        umgr.setAuthorizableActions(new AuthorizableAction[] {testAction});
+
+        // import user
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"t\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
+                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:User</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:password\" sv:type=\"String\"><sv:value>pw</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>tPrincipal</sv:value></sv:property>" +
+                "</sv:node>";
+
+        NodeImpl target = (NodeImpl) sImpl.getNode(umgr.getUsersPath());
+        try {
+            doImport(target, xml);
+            assertEquals(testAction.id, "t");
+            assertEquals(testAction.pw, "pw");
+        } finally {
+            sImpl.refresh(false);
+        }
+    }
+
+    public void testActionExecutionForGroup() throws Exception {
+        TestAction testAction = new TestAction();
+
+        umgr.setAuthorizableActions(new AuthorizableAction[] {testAction});
+
+        // import group
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"g\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
+                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:Group</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>b2f5ff47-4366-31b6-a533-d8dc3614845d</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>gPrincipal</sv:value></sv:property>" +
+                "</sv:node>";
+
+        NodeImpl target = (NodeImpl) sImpl.getNode(umgr.getGroupsPath());
+        try {
+            doImport(target, xml);
+            assertEquals(testAction.id, "g");
+            assertNull(testAction.pw);
+        } finally {
+            sImpl.refresh(false);
+        }
+    }
+
+    public void testAccessControlActionExecutionForUser() throws Exception {
+        AccessControlAction a1 = new AccessControlAction();
+        a1.setUserPrivilegeNames(Privilege.JCR_ALL);
+
+        umgr.setAuthorizableActions(new AuthorizableAction[] {a1});
+
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"t\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
+                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:User</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:password\" sv:type=\"String\"><sv:value>{sha1}8efd86fb78a56a5145ed7739dcb00c78581c5375</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>tPrincipal</sv:value></sv:property>" +
+                "</sv:node>";
+
+        NodeImpl target = (NodeImpl) sImpl.getNode(umgr.getUsersPath());
+        try {
+            doImport(target, xml);
+
+            Authorizable a = umgr.getAuthorizable("t");
+            assertNotNull(a);
+            assertFalse(a.isGroup());
+
+            AccessControlManager acMgr = sImpl.getAccessControlManager();
+            AccessControlPolicy[] policies = acMgr.getPolicies(a.getPath());
+            assertNotNull(policies);
+            assertEquals(1, policies.length);
+            assertTrue(policies[0] instanceof AccessControlList);
+
+            AccessControlEntry[] aces = ((AccessControlList) policies[0]).getAccessControlEntries();
+            assertEquals(1, aces.length);
+            assertEquals("tPrincipal", aces[0].getPrincipal().getName());
+
+        } finally {
+            sImpl.refresh(false);
+        }
+    }
+
+    public void testAccessControlActionExecutionForUser2() throws Exception {
+        AccessControlAction a1 = new AccessControlAction();
+        a1.setUserPrivilegeNames(Privilege.JCR_ALL);
+
+        umgr.setAuthorizableActions(new AuthorizableAction[] {a1});
+
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"t\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
+                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:User</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>e358efa4-89f5-3062-b10d-d7316b65649e</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>tPrincipal</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:password\" sv:type=\"String\"><sv:value>{sha1}8efd86fb78a56a5145ed7739dcb00c78581c5375</sv:value></sv:property>" +
+                "</sv:node>";
+
+        NodeImpl target = (NodeImpl) sImpl.getNode(umgr.getUsersPath());
+        try {
+            doImport(target, xml);
+
+            Authorizable a = umgr.getAuthorizable("t");
+            assertNotNull(a);
+            assertFalse(a.isGroup());
+
+            AccessControlManager acMgr = sImpl.getAccessControlManager();
+            AccessControlPolicy[] policies = acMgr.getPolicies(a.getPath());
+            assertNotNull(policies);
+            assertEquals(1, policies.length);
+            assertTrue(policies[0] instanceof AccessControlList);
+
+            AccessControlEntry[] aces = ((AccessControlList) policies[0]).getAccessControlEntries();
+            assertEquals(1, aces.length);
+            assertEquals("tPrincipal", aces[0].getPrincipal().getName());
+
+        } finally {
+            sImpl.refresh(false);
+        }
+    }
+
+    public void testAccessControlActionExecutionForGroup() throws Exception {
+        AccessControlAction a1 = new AccessControlAction();
+        a1.setGroupPrivilegeNames(Privilege.JCR_READ);
+
+        umgr.setAuthorizableActions(new AuthorizableAction[] {a1});
+
+        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+                "<sv:node sv:name=\"g\" xmlns:mix=\"http://www.jcp.org/jcr/mix/1.0\" xmlns:nt=\"http://www.jcp.org/jcr/nt/1.0\" xmlns:fn_old=\"http://www.w3.org/2004/10/xpath-functions\" xmlns:fn=\"http://www.w3.org/2005/xpath-functions\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" xmlns:rep=\"internal\" xmlns:jcr=\"http://www.jcp.org/jcr/1.0\">" +
+                "   <sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\"><sv:value>rep:Group</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"jcr:uuid\" sv:type=\"String\"><sv:value>b2f5ff47-4366-31b6-a533-d8dc3614845d</sv:value></sv:property>" +
+                "   <sv:property sv:name=\"rep:principalName\" sv:type=\"String\"><sv:value>gPrincipal</sv:value></sv:property>" +
+                "</sv:node>";
+
+        NodeImpl target = (NodeImpl) sImpl.getNode(umgr.getGroupsPath());
+        try {
+            doImport(target, xml);
+
+            Authorizable a = umgr.getAuthorizable("g");
+            assertNotNull(a);
+            assertTrue(a.isGroup());
+
+            AccessControlManager acMgr = sImpl.getAccessControlManager();
+            AccessControlPolicy[] policies = acMgr.getPolicies(a.getPath());
+            assertNotNull(policies);
+            assertEquals(1, policies.length);
+            assertTrue(policies[0] instanceof AccessControlList);
+
+            AccessControlEntry[] aces = ((AccessControlList) policies[0]).getAccessControlEntries();
+            assertEquals(1, aces.length);
+            assertEquals("gPrincipal", aces[0].getPrincipal().getName());
+
+        } finally {
+            sImpl.refresh(false);
+        }
+    }
+
+    //--------------------------------------------------------------------------
+
     private void doImport(NodeImpl target, String xml) throws IOException, SAXException, RepositoryException {
         InputStream in = new ByteArrayInputStream(xml.getBytes("UTF-8"));
         SessionImporter importer = new SessionImporter(target, sImpl,
@@ -1377,6 +1558,25 @@ public class UserImporterTest extends AbstractJCRTest {
 
     }
 
+    private final class TestAction implements AuthorizableAction {
+        private String id;
+        private String pw;
+
+        public void onCreate(Group group, Session session) throws RepositoryException {
+            id = group.getID();
+        }
+        public void onCreate(User user, String password, Session session) throws RepositoryException {
+            id = user.getID();
+            pw = password;
+        }
+        public void onRemove(Authorizable authorizable, Session session) throws RepositoryException {
+            // ignore
+        }
+        public void onPasswordChange(User user, String newPassword, Session session) throws RepositoryException {
+            pw = newPassword;
+        }
+    }
+
     private final class DummySession implements JackrabbitSession {
 
         private DummySession() {
@@ -1393,6 +1593,10 @@ public class UserImporterTest extends AbstractJCRTest {
                     return null;
                 }
 
+                public <T extends Authorizable> T getAuthorizable(String id, Class<T> authorizableClass) throws RepositoryException {
+                    return null;
+                }
+
                 public Authorizable getAuthorizable(Principal principal) throws RepositoryException {
                     return null;
                 }
@@ -1421,6 +1625,10 @@ public class UserImporterTest extends AbstractJCRTest {
                     return null;
                 }
 
+                public User createSystemUser(String userID, String intermediatePath) throws AuthorizableExistsException, RepositoryException {
+                    return null;
+                }
+
                 public Group createGroup(String groupID) throws AuthorizableExistsException, RepositoryException {
                 	return null;
                 }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserManagerImplTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserManagerImplTest.java
index 4ff29e2..0b8d396 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserManagerImplTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/user/UserManagerImplTest.java
@@ -25,30 +25,22 @@ import org.apache.jackrabbit.api.security.user.UserManager;
 import org.apache.jackrabbit.core.SessionImpl;
 import org.apache.jackrabbit.core.security.TestPrincipal;
 import org.apache.jackrabbit.core.security.principal.EveryonePrincipal;
-import org.apache.jackrabbit.core.security.user.action.AccessControlAction;
-import org.apache.jackrabbit.core.security.user.action.AuthorizableAction;
-import org.apache.jackrabbit.core.security.user.action.ClearMembershipAction;
-import org.apache.jackrabbit.test.NotExecutableException;
 import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
+import org.apache.jackrabbit.test.NotExecutableException;
 
 import javax.jcr.Credentials;
+import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.SimpleCredentials;
-import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.Value;
-import javax.jcr.Node;
-import javax.jcr.security.AccessControlList;
-import javax.jcr.security.AccessControlManager;
-import javax.jcr.security.AccessControlPolicy;
-import javax.jcr.security.AccessControlPolicyIterator;
 import java.security.Principal;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.List;
-import java.util.ArrayList;
 
 /**
  * <code>UserManagerImplTest</code>...
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/stats/QueryStatCoreTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/stats/QueryStatCoreTest.java
new file mode 100644
index 0000000..5d14c15
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/stats/QueryStatCoreTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.stats;
+
+import static javax.jcr.query.Query.JCR_SQL2;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.jackrabbit.stats.QueryStatCore;
+import org.apache.jackrabbit.stats.QueryStatImpl;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+
+/**
+ * Query stats test cases.
+ */
+public class QueryStatCoreTest extends AbstractJCRTest {
+
+    private QueryStatCore queryStat;
+
+    private AtomicLong token = new AtomicLong(System.currentTimeMillis());
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        queryStat = new QueryStatImpl();
+        queryStat.setEnabled(true);
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private void runRandomQuery() throws Exception {
+        String sql = "SELECT * FROM [nt:unstructured] as t where CONTAINS(t, '"
+                + token.getAndIncrement() + "') ";
+        queryStat.logQuery(JCR_SQL2, sql, 5);
+    }
+
+    public void testPopularQuery() throws Exception {
+
+        int size = 15;
+        queryStat.setPopularQueriesQueueSize(size);
+
+        // test clear
+        runRandomQuery();
+        queryStat.clearPopularQueriesQueue();
+        assertEquals(0, queryStat.getPopularQueries().length);
+
+        // test run one
+        queryStat.clearPopularQueriesQueue();
+        runRandomQuery();
+        assertEquals(1, queryStat.getPopularQueries().length);
+
+        // run more than max size
+        queryStat.clearPopularQueriesQueue();
+        for (int i = 0; i < size + 5; i++) {
+            runRandomQuery();
+        }
+        assertEquals(size, queryStat.getPopularQueries().length);
+
+        // test shrink
+        int newSize = 5;
+        queryStat.setPopularQueriesQueueSize(newSize);
+        assertEquals(newSize, queryStat.getPopularQueries().length);
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/stats/TestAll.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/stats/TestAll.java
new file mode 100644
index 0000000..426ee30
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/stats/TestAll.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.stats;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test suite that includes all test cases for the stats module.
+ */
+public class TestAll extends TestCase {
+
+    /**
+     * Returns a <code>Test</code> suite that executes all tests inside this
+     * package.
+     *
+     * @return a <code>Test</code> suite that executes all tests inside this
+     *         package.
+     */
+    public static Test suite() {
+        TestSuite suite = new TestSuite("Stats tests");
+
+        suite.addTestSuite(QueryStatCoreTest.class);
+
+        return suite;
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/stats/TimeSeriesRecorderTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/stats/TimeSeriesRecorderTest.java
deleted file mode 100644
index 11cb4b6..0000000
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/stats/TimeSeriesRecorderTest.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.stats;
-
-import java.util.concurrent.atomic.AtomicLong;
-
-import org.apache.jackrabbit.api.stats.RepositoryStatistics;
-
-import junit.framework.TestCase;
-
-public class TimeSeriesRecorderTest extends TestCase {
-
-    public void testCounter() {
-        TimeSeriesRecorder recorder = new TimeSeriesRecorder(
-                RepositoryStatistics.Type.SESSION_READ_COUNTER);
-        AtomicLong counter = recorder.getCounter();
-
-        // initial values
-        assertValues(recorder.getValuePerSecond());
-        assertValues(recorder.getValuePerMinute());
-        assertValues(recorder.getValuePerHour());
-        assertValues(recorder.getValuePerWeek());
-
-        // no changes in first second
-        recorder.recordOneSecond();
-        assertValues(recorder.getValuePerSecond());
-        assertValues(recorder.getValuePerMinute());
-        assertValues(recorder.getValuePerHour());
-        assertValues(recorder.getValuePerWeek());
-
-        // one increment in second
-        counter.incrementAndGet();
-        recorder.recordOneSecond();
-        assertValues(recorder.getValuePerSecond(), 1);
-        assertValues(recorder.getValuePerMinute());
-        assertValues(recorder.getValuePerHour());
-        assertValues(recorder.getValuePerWeek());
-
-        // two increments in second
-        counter.incrementAndGet();
-        counter.incrementAndGet();
-        recorder.recordOneSecond();
-        assertValues(recorder.getValuePerSecond(), 2, 1);
-        assertValues(recorder.getValuePerMinute());
-        assertValues(recorder.getValuePerHour());
-        assertValues(recorder.getValuePerWeek());
-
-        // no changes in a second
-        recorder.recordOneSecond();
-        assertValues(recorder.getValuePerSecond(), 0, 2, 1);
-        assertValues(recorder.getValuePerMinute());
-        assertValues(recorder.getValuePerHour());
-        assertValues(recorder.getValuePerWeek());
-
-        // ten increments in a second
-        counter.addAndGet(10);
-        recorder.recordOneSecond();
-        assertValues(recorder.getValuePerSecond(), 10, 0, 2, 1);
-        assertValues(recorder.getValuePerMinute());
-        assertValues(recorder.getValuePerHour());
-        assertValues(recorder.getValuePerWeek());
-
-        // one minute
-        for (int i = 0; i < 60; i++) {
-            recorder.recordOneSecond();
-        }
-        assertValues(recorder.getValuePerSecond());
-        assertValues(recorder.getValuePerMinute(), 13);
-        assertValues(recorder.getValuePerHour());
-        assertValues(recorder.getValuePerWeek());
-
-        // second minute
-        for (int i = 0; i < 60; i++) {
-            recorder.recordOneSecond();
-        }
-        assertValues(recorder.getValuePerSecond());
-        assertValues(recorder.getValuePerMinute(), 0, 13);
-        assertValues(recorder.getValuePerHour());
-        assertValues(recorder.getValuePerWeek());
-
-        // one hour
-        for (int i = 0; i < 60 * 60; i++) {
-            recorder.recordOneSecond();
-        }
-        assertValues(recorder.getValuePerSecond());
-        assertValues(recorder.getValuePerMinute());
-        assertValues(recorder.getValuePerHour(), 13);
-        assertValues(recorder.getValuePerWeek());
-
-        // one week
-        for (int i = 0; i < 7 * 24 * 60 * 60; i++) {
-            recorder.recordOneSecond();
-        }
-        assertValues(recorder.getValuePerSecond());
-        assertValues(recorder.getValuePerMinute());
-        assertValues(recorder.getValuePerHour());
-        assertValues(recorder.getValuePerWeek(), 13);
-    }
-
-    private void assertValues(long[] values, long... expected) {
-        for (int i = 0; i < expected.length; i++) {
-            assertEquals(expected[i], values[values.length - i - 1]);
-        }
-        for (int i = expected.length; i < values.length; i++) {
-            assertEquals(0, values[values.length - i - 1]);
-        }
-    }
-
-}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/util/db/ConnectionFactoryTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/util/db/ConnectionFactoryTest.java
index e7c36c1..46fbf6b 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/util/db/ConnectionFactoryTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/util/db/ConnectionFactoryTest.java
@@ -151,7 +151,7 @@ public class ConnectionFactoryTest extends TestCase {
         assertTrue(ds.getDefaultAutoCommit());
         assertFalse(ds.getTestOnBorrow());
         assertTrue(ds.getTestWhileIdle());
-        assertEquals(1000, ds.getTimeBetweenEvictionRunsMillis());
+        assertEquals(600000, ds.getTimeBetweenEvictionRunsMillis());
         assertTrue(ds.isPoolPreparedStatements());
         assertEquals(-1, ds.getMaxOpenPreparedStatements());
     }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/value/BinaryValueTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/value/BinaryValueTest.java
index 9e0edd0..f9d509b 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/value/BinaryValueTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/value/BinaryValueTest.java
@@ -34,7 +34,7 @@ import org.apache.jackrabbit.test.AbstractJCRTest;
  * <li>dispose Binary</li>
  * </ul>
  * do not throw an exception.
- * <p/>
+ * <p>
  * See also JCR-2238.
  */
 public class BinaryValueTest extends AbstractJCRTest {
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/value/ReferenceBinaryTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/value/ReferenceBinaryTest.java
new file mode 100644
index 0000000..d77c78b
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/value/ReferenceBinaryTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.value;
+
+import javax.jcr.Binary;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.jackrabbit.api.ReferenceBinary;
+import org.apache.jackrabbit.commons.jackrabbit.SimpleReferenceBinary;
+import org.apache.jackrabbit.core.data.RandomInputStream;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+
+/**
+ * Testcase for JCR-3534.
+ */
+public class ReferenceBinaryTest extends AbstractJCRTest {
+
+    private static final int STREAM_LENGTH = 256 * 1024;
+
+    public void testReferenceBinaryExchangeWithSharedRepository() throws Exception {
+
+        Session firstSession = superuser;
+
+        // create a binary
+        Binary b = vf.createBinary(new RandomInputStream(1, STREAM_LENGTH));
+
+        ReferenceBinary referenceBinary = null;
+        if (b instanceof ReferenceBinary) {
+            referenceBinary = (ReferenceBinary) b;
+        }
+
+        assertNotNull(referenceBinary);
+
+        assertNotNull(referenceBinary.getReference());
+
+        // in the current test the message is exchanged via repository which is shared as well
+        // put the reference message value in a property on a node
+        String newNode = "sample_" + System.nanoTime();
+        firstSession.getRootNode().addNode(newNode).setProperty("reference", referenceBinary.getReference());
+
+        // save the first session
+        firstSession.save();
+
+        // get a second session over the same repository / ds
+        Session secondSession = getHelper().getRepository().login(new SimpleCredentials("admin", "admin".toCharArray()));
+
+        // read the binary referenced by the referencing binary
+        String reference = secondSession.getRootNode().getNode(newNode).getProperty("reference").getString();
+
+        ReferenceBinary ref = new SimpleReferenceBinary(reference);
+
+        assertEquals(b, secondSession.getValueFactory().createValue(ref).getBinary());
+
+    }
+
+}
\ No newline at end of file
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/value/TestAll.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/value/TestAll.java
index ddde9e3..68a5de9 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/value/TestAll.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/value/TestAll.java
@@ -36,6 +36,7 @@ public class TestAll extends TestCase {
         suite.addTestSuite(InternalValueFactoryTest.class);
         suite.addTestSuite(InternalValueTest.class);
         suite.addTestSuite(PathTest.class);
+        suite.addTestSuite(ReferenceBinaryTest.class);
 
         return suite;
     }
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/version/CopyFrozenUuidTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/version/CopyFrozenUuidTest.java
new file mode 100644
index 0000000..6cd3f11
--- /dev/null
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/version/CopyFrozenUuidTest.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.version;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.version.Version;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+
+/**
+ * Tests the case when a node already has a manual set JcrConstants.JCR_FROZENUUID property and is versioned.
+ * The manual set frozenUuid will overwrite the one that is automatically assigned by the VersionManager, which should not happen
+ */
+public class CopyFrozenUuidTest extends AbstractJCRTest {
+
+    public void testCopyFrozenUuidProperty() throws Exception {
+        Node firstNode = testRootNode.addNode(nodeName1);
+        firstNode.setPrimaryType(JcrConstants.NT_UNSTRUCTURED);
+        firstNode.addMixin(JcrConstants.MIX_VERSIONABLE);
+        firstNode.getSession().save();
+
+        // create version for the node
+        Version firstNodeVersion = firstNode.checkin();
+        firstNode.checkout();
+
+        Node secondNode = testRootNode.addNode(nodeName2);
+        secondNode.setPrimaryType(JcrConstants.NT_UNSTRUCTURED);
+        secondNode.addMixin(JcrConstants.MIX_VERSIONABLE);
+        Property firstNodeVersionFrozenUuid = firstNodeVersion.getFrozenNode().getProperty(JcrConstants.JCR_FROZENUUID);
+        secondNode.setProperty(JcrConstants.JCR_FROZENUUID, firstNodeVersionFrozenUuid.getValue());
+        secondNode.getSession().save();
+
+        // create version of the second node
+        Version secondNodeVersion = secondNode.checkin();
+        secondNode.checkout();
+
+        // frozenUuid from the second node version node should not be the same as the one from the first node version
+        Property secondBodeVersionFrozenUuid = secondNodeVersion.getFrozenNode().getProperty(JcrConstants.JCR_FROZENUUID);
+        assertFalse(JcrConstants.JCR_FROZENUUID + " should not be the same for two different versions of different nodes! ", 
+                secondBodeVersionFrozenUuid.getValue().equals(firstNodeVersionFrozenUuid.getValue()));
+    }
+
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/version/RestoreNodeWithSNSTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/version/RestoreNodeWithSNSTest.java
index ebd3c5d..ec3b1a9 100755
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/version/RestoreNodeWithSNSTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/version/RestoreNodeWithSNSTest.java
@@ -1,67 +1,67 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.core.version;
-
-import javax.jcr.Node;
-import javax.jcr.version.Version;
-import javax.jcr.version.VersionManager;
-
-import org.apache.jackrabbit.test.AbstractJCRTest;
-
-/**
- * Test case for JCR-2930
- */
-public class RestoreNodeWithSNSTest extends AbstractJCRTest {
-
-    public void testRestoreWithSNS() throws Exception {
-        
-        int childCount = 5;
-        
-        // create a test node with /childCount/ children with the same name
-        Node n = testRootNode.addNode(nodeName1);
-        n.addMixin(mixVersionable);
-        for (int i = 0; i < childCount; i++) {
-            Node child = n.addNode(nodeName2);
-            child.setProperty("name", nodeName2 + i);
-        }
-        testRootNode.getSession().save();
-
-        // check the number of children
-        assertEquals(childCount, n.getNodes().getSize());
-
-        VersionManager vm = testRootNode.getSession().getWorkspace()
-                .getVersionManager();
-        vm.checkin(n.getPath());
-
-        // modify one child
-        vm.checkout(n.getPath());
-        n.getNode(nodeName2).setProperty("name", "modified");
-        testRootNode.getSession().save();
-
-        // check the number of children again
-        assertEquals(childCount, n.getNodes().getSize());
-
-        // restore base versiob
-        Version baseVersion = vm.getBaseVersion(n.getPath());
-        vm.restore(baseVersion, true);
-
-        n.getSession().refresh(false);
-
-        // check the number of children again
-        assertEquals(childCount, n.getNodes().getSize());
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.version;
+
+import javax.jcr.Node;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionManager;
+
+import org.apache.jackrabbit.test.AbstractJCRTest;
+
+/**
+ * Test case for JCR-2930
+ */
+public class RestoreNodeWithSNSTest extends AbstractJCRTest {
+
+    public void testRestoreWithSNS() throws Exception {
+        
+        int childCount = 5;
+        
+        // create a test node with /childCount/ children with the same name
+        Node n = testRootNode.addNode(nodeName1);
+        n.addMixin(mixVersionable);
+        for (int i = 0; i < childCount; i++) {
+            Node child = n.addNode(nodeName2);
+            child.setProperty("name", nodeName2 + i);
+        }
+        testRootNode.getSession().save();
+
+        // check the number of children
+        assertEquals(childCount, n.getNodes().getSize());
+
+        VersionManager vm = testRootNode.getSession().getWorkspace()
+                .getVersionManager();
+        vm.checkin(n.getPath());
+
+        // modify one child
+        vm.checkout(n.getPath());
+        n.getNode(nodeName2).setProperty("name", "modified");
+        testRootNode.getSession().save();
+
+        // check the number of children again
+        assertEquals(childCount, n.getNodes().getSize());
+
+        // restore base versiob
+        Version baseVersion = vm.getBaseVersion(n.getPath());
+        vm.restore(baseVersion, true);
+
+        n.getSession().refresh(false);
+
+        // check the number of children again
+        assertEquals(childCount, n.getNodes().getSize());
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/version/TestAll.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/version/TestAll.java
index b58687a..f3f6800 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/version/TestAll.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/version/TestAll.java
@@ -33,6 +33,7 @@ public class TestAll extends TestCase {
     public static Test suite() {
         TestSuite suite = new TestSuite("Version tests");
         suite.addTestSuite(CheckinRemoveVersionTest.class);
+        suite.addTestSuite(CopyFrozenUuidTest.class);
         suite.addTestSuite(InternalVersionHistoryImplTest.class);
         suite.addTestSuite(RemoveVersionLabelTest.class);
         suite.addTestSuite(RestoreTest.class);
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/xml/AccessControlImporterTest.java b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/xml/AccessControlImporterTest.java
index b762530..a0889ca 100644
--- a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/xml/AccessControlImporterTest.java
+++ b/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/xml/AccessControlImporterTest.java
@@ -45,6 +45,7 @@ import javax.jcr.security.AccessControlPolicyIterator;
 import javax.jcr.security.Privilege;
 
 import java.io.ByteArrayInputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.util.Arrays;
 import java.util.Collections;
@@ -476,6 +477,33 @@ public class AccessControlImporterTest extends AbstractJCRTest {
     }
 
     /**
+     * Imports a resource-based ACL containing a single entry.
+     *
+     * @throws Exception
+     */
+    public void testImportACLUnknownFail() throws Exception {
+        try {
+            NodeImpl target = (NodeImpl) testRootNode.addNode(nodeName1);
+            target.addMixin("rep:AccessControllable");
+
+            InputStream in = new ByteArrayInputStream(XML_POLICY_TREE_4.getBytes("UTF-8"));
+            PseudoConfig config = new PseudoConfig();
+            ((AccessControlImporter) config.getProtectedItemImporters().get(0)).setImportBehavior("default");
+            SessionImporter importer = new SessionImporter(target, sImpl,
+                    ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW, config);
+            ImportHandler ih = new ImportHandler(importer, sImpl);
+            try {
+                new ParsingContentHandler(ih).parse(in);
+                fail("importing unknown principal should fail based on configuration.");
+            } catch (Exception e) {
+                // ok
+            }
+        } finally {
+            superuser.refresh(false);
+        }
+    }
+
+    /**
      * Imports a resource-based ACL containing a single entry for a policy that
      * already exists.
      *
diff --git a/jackrabbit-core/src/test/repository/repository.xml b/jackrabbit-core/src/test/repository/repository.xml
index 90e4876..c3e80fa 100644
--- a/jackrabbit-core/src/test/repository/repository.xml
+++ b/jackrabbit-core/src/test/repository/repository.xml
@@ -30,7 +30,7 @@
     <!--
         data store configuration
     -->
-	<DataStore class="org.apache.jackrabbit.core.data.FileDataStore"/>
+    <DataStore class="org.apache.jackrabbit.core.data.FileDataStore"/>
     <!--
         sample database data store configuration
         <DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
diff --git a/jackrabbit-core/src/test/repository/workspaces/default/workspace.xml b/jackrabbit-core/src/test/repository/workspaces/default/workspace.xml
index d5bf0a2..035b4c8 100644
--- a/jackrabbit-core/src/test/repository/workspaces/default/workspace.xml
+++ b/jackrabbit-core/src/test/repository/workspaces/default/workspace.xml
@@ -15,7 +15,7 @@
    See the License for the specific language governing permissions and
    limitations under the License.
   -->
-<Workspace name="default">
+<Workspace name="default" defaultLockTimeout="86400">
   <!--
       virtual file system of the workspace:
       class: FQN of class implementing FileSystem interface
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v1/index/_0/_2.cfs b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/index/_0/_2.cfs
new file mode 100644
index 0000000..3794bf7
Binary files /dev/null and b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/index/_0/_2.cfs differ
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v1/index/_0/deletable b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/index/_0/deletable
new file mode 100644
index 0000000..593f470
Binary files /dev/null and b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/index/_0/deletable differ
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v1/index/_0/segments b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/index/_0/segments
new file mode 100644
index 0000000..ffe36ef
Binary files /dev/null and b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/index/_0/segments differ
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v1/index/indexes b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/index/indexes
new file mode 100644
index 0000000..97ab954
Binary files /dev/null and b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/index/indexes differ
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v1/items/5a/9a/d0fcc7f542bbb435bcb9ed30a2e2.n b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/items/5a/9a/d0fcc7f542bbb435bcb9ed30a2e2.n
new file mode 100644
index 0000000..28a85bf
Binary files /dev/null and b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/items/5a/9a/d0fcc7f542bbb435bcb9ed30a2e2.n differ
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v1/items/ca/fe/babecafebabecafebabecafebabe.n b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/items/ca/fe/babecafebabecafebabecafebabe.n
new file mode 100644
index 0000000..1bbc794
Binary files /dev/null and b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/items/ca/fe/babecafebabecafebabecafebabe.n differ
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v1/items/de/ad/beefcafebabecafebabecafebabe.n b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/items/de/ad/beefcafebabecafebabecafebabe.n
new file mode 100644
index 0000000..ef27350
Binary files /dev/null and b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/items/de/ad/beefcafebabecafebabecafebabe.n differ
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v1/names.properties b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/names.properties
new file mode 100644
index 0000000..3174948
--- /dev/null
+++ b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/names.properties
@@ -0,0 +1,5 @@
+#string index
+#Wed Apr 02 18:14:08 CEST 2008
+root=1
+system=0
+unstructured=2
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v1/namespaces.properties b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/namespaces.properties
new file mode 100644
index 0000000..3aea050
--- /dev/null
+++ b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/namespaces.properties
@@ -0,0 +1,6 @@
+#string index
+#Mon Apr 07 10:28:09 CEST 2008
+http\://www.jcp.org/jcr/1.0=1
+internal=0
+http\://www.jcp.org/jcr/nt/1.0=2
+=3
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v1/workspace.xml b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/workspace.xml
new file mode 100644
index 0000000..84ebb47
--- /dev/null
+++ b/jackrabbit-core/src/test/repository/workspaces/index-format-v1/workspace.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+<Workspace name="index-format-v1">
+
+  <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+    <param name="path" value="${wsp.home}" />
+  </FileSystem>
+
+  <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+     <param name="url" value="jdbc:derby:${wsp.home}/db;create=true"/>
+     <param name="schemaObjectPrefix" value="${wsp.name}_"/>
+  </PersistenceManager>
+
+  <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+    <param name="path" value="${wsp.home}/index" />
+  </SearchIndex>
+</Workspace>
+
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v2/index/_0/_0.cfs b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/index/_0/_0.cfs
new file mode 100644
index 0000000..dd6b7ec
Binary files /dev/null and b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/index/_0/_0.cfs differ
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v2/index/_0/segments.gen b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/index/_0/segments.gen
new file mode 100644
index 0000000..e9fa600
Binary files /dev/null and b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/index/_0/segments.gen differ
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v2/index/_0/segments_1 b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/index/_0/segments_1
new file mode 100644
index 0000000..64960a8
Binary files /dev/null and b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/index/_0/segments_1 differ
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v2/index/_0/segments_3 b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/index/_0/segments_3
new file mode 100644
index 0000000..4da6ab8
Binary files /dev/null and b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/index/_0/segments_3 differ
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v2/index/indexes b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/index/indexes
new file mode 100644
index 0000000..97ab954
Binary files /dev/null and b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/index/indexes differ
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v2/items/c9/bb/26c0edf0408b8ab22e88c1edc593.n b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/items/c9/bb/26c0edf0408b8ab22e88c1edc593.n
new file mode 100644
index 0000000..28a85bf
Binary files /dev/null and b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/items/c9/bb/26c0edf0408b8ab22e88c1edc593.n differ
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v2/items/ca/fe/babecafebabecafebabecafebabe.n b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/items/ca/fe/babecafebabecafebabecafebabe.n
new file mode 100644
index 0000000..ad76578
Binary files /dev/null and b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/items/ca/fe/babecafebabecafebabecafebabe.n differ
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v2/items/de/ad/beefcafebabecafebabecafebabe.n b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/items/de/ad/beefcafebabecafebabecafebabe.n
new file mode 100644
index 0000000..ef27350
Binary files /dev/null and b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/items/de/ad/beefcafebabecafebabecafebabe.n differ
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v2/names.properties b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/names.properties
new file mode 100644
index 0000000..5bc9630
--- /dev/null
+++ b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/names.properties
@@ -0,0 +1,5 @@
+#string index
+#Wed Apr 02 18:21:04 CEST 2008
+root=1
+system=0
+unstructured=2
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v2/namespaces.properties b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/namespaces.properties
new file mode 100644
index 0000000..95b3d95
--- /dev/null
+++ b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/namespaces.properties
@@ -0,0 +1,6 @@
+#string index
+#Mon Apr 07 10:40:50 CEST 2008
+http\://www.jcp.org/jcr/1.0=1
+internal=0
+http\://www.jcp.org/jcr/nt/1.0=2
+=3
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v2/workspace.xml b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/workspace.xml
new file mode 100644
index 0000000..19e4f3c
--- /dev/null
+++ b/jackrabbit-core/src/test/repository/workspaces/index-format-v2/workspace.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+<Workspace name="index-format-v2">
+
+  <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+    <param name="path" value="${wsp.home}" />
+  </FileSystem>
+
+  <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+     <param name="url" value="jdbc:derby:${wsp.home}/db;create=true"/>
+     <param name="schemaObjectPrefix" value="${wsp.name}_"/>
+  </PersistenceManager>
+
+  <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+    <param name="path" value="${wsp.home}/index" />
+  </SearchIndex>
+</Workspace>
+
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v3/items/c9/bb/26c0edf0408b8ab22e88c1edc593.n b/jackrabbit-core/src/test/repository/workspaces/index-format-v3/items/c9/bb/26c0edf0408b8ab22e88c1edc593.n
new file mode 100644
index 0000000..28a85bf
Binary files /dev/null and b/jackrabbit-core/src/test/repository/workspaces/index-format-v3/items/c9/bb/26c0edf0408b8ab22e88c1edc593.n differ
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v3/items/ca/fe/babecafebabecafebabecafebabe.n b/jackrabbit-core/src/test/repository/workspaces/index-format-v3/items/ca/fe/babecafebabecafebabecafebabe.n
new file mode 100644
index 0000000..ad76578
Binary files /dev/null and b/jackrabbit-core/src/test/repository/workspaces/index-format-v3/items/ca/fe/babecafebabecafebabecafebabe.n differ
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v3/items/de/ad/beefcafebabecafebabecafebabe.n b/jackrabbit-core/src/test/repository/workspaces/index-format-v3/items/de/ad/beefcafebabecafebabecafebabe.n
new file mode 100644
index 0000000..ef27350
Binary files /dev/null and b/jackrabbit-core/src/test/repository/workspaces/index-format-v3/items/de/ad/beefcafebabecafebabecafebabe.n differ
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v3/names.properties b/jackrabbit-core/src/test/repository/workspaces/index-format-v3/names.properties
new file mode 100644
index 0000000..5bc9630
--- /dev/null
+++ b/jackrabbit-core/src/test/repository/workspaces/index-format-v3/names.properties
@@ -0,0 +1,5 @@
+#string index
+#Wed Apr 02 18:21:04 CEST 2008
+root=1
+system=0
+unstructured=2
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v3/namespaces.properties b/jackrabbit-core/src/test/repository/workspaces/index-format-v3/namespaces.properties
new file mode 100644
index 0000000..95b3d95
--- /dev/null
+++ b/jackrabbit-core/src/test/repository/workspaces/index-format-v3/namespaces.properties
@@ -0,0 +1,6 @@
+#string index
+#Mon Apr 07 10:40:50 CEST 2008
+http\://www.jcp.org/jcr/1.0=1
+internal=0
+http\://www.jcp.org/jcr/nt/1.0=2
+=3
diff --git a/jackrabbit-core/src/test/repository/workspaces/index-format-v3/workspace.xml b/jackrabbit-core/src/test/repository/workspaces/index-format-v3/workspace.xml
new file mode 100644
index 0000000..e3874cb
--- /dev/null
+++ b/jackrabbit-core/src/test/repository/workspaces/index-format-v3/workspace.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+<Workspace name="index-format-v3">
+
+  <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+    <param name="path" value="${wsp.home}" />
+  </FileSystem>
+
+  <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+     <param name="url" value="jdbc:derby:${wsp.home}/db;create=true"/>
+     <param name="schemaObjectPrefix" value="${wsp.name}_"/>
+  </PersistenceManager>
+
+  <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+    <param name="path" value="${wsp.home}/index" />
+  </SearchIndex>
+</Workspace>
+
diff --git a/jackrabbit-core/src/test/repository/workspaces/indexing-test/indexing-configuration.xml b/jackrabbit-core/src/test/repository/workspaces/indexing-test/indexing-configuration.xml
index 2a0ffab..eb7f91d 100644
--- a/jackrabbit-core/src/test/repository/workspaces/indexing-test/indexing-configuration.xml
+++ b/jackrabbit-core/src/test/repository/workspaces/indexing-test/indexing-configuration.xml
@@ -34,6 +34,7 @@
 
     <index-rule nodeType="nt:unstructured" condition="@rule='excerpt'">
         <property useInExcerpt="false">title</property>
+        <property>foo</property><!-- needed to test JCR-3610 -->
         <property>text</property>
     </index-rule>
 
diff --git a/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/cluster/repository-h2.xml b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/cluster/repository-h2.xml
index d15f787..6b73992 100644
--- a/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/cluster/repository-h2.xml
+++ b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/cluster/repository-h2.xml
@@ -28,7 +28,7 @@
         (e.g. registered namespaces, custom node types, etc.)
     -->
     <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-        <param name="url" value="jdbc:h2:tcp://localhost:9001,localhost:9002/db"/>
+        <param name="url" value="jdbc:h2:${rep.home}/../db"/>
         <param name="schemaObjectPrefix" value="fs_"/>
         <param name="user" value="sa"/>
         <param name="password" value="sa"/>
@@ -38,7 +38,7 @@
         data store configuration
     -->
     <DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
-        <param name="url" value="jdbc:h2:tcp://localhost:9001,localhost:9002/db"/>
+        <param name="url" value="jdbc:h2:${rep.home}/../db"/>
         <param name="schemaObjectPrefix" value="datastore_"/>
         <param name="user" value="sa"/>
         <param name="password" value="sa"/>
@@ -103,7 +103,7 @@
             class: FQN of class implementing the PersistenceManager interface
         -->
         <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
-            <param name="url" value="jdbc:h2:tcp://localhost:9001,localhost:9002/db"/>
+            <param name="url" value="jdbc:h2:${rep.home}/../db"/>
             <param name="schemaObjectPrefix" value="ws_${wsp.name}_"/>
             <param name="user" value="sa"/>
             <param name="password" value="sa"/>
@@ -138,7 +138,7 @@
             implementations.
         -->
         <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
-            <param name="url" value="jdbc:h2:tcp://localhost:9001,localhost:9002/db"/>
+            <param name="url" value="jdbc:h2:${rep.home}/../db"/>
             <param name="schemaObjectPrefix" value="version_"/>
             <param name="user" value="sa"/>
             <param name="password" value="sa"/>
@@ -160,7 +160,7 @@
     <Cluster>
         <Journal class="org.apache.jackrabbit.core.journal.DatabaseJournal">
             <param name="driver" value="org.h2.Driver" />
-            <param name="url" value="jdbc:h2:tcp://localhost:9001,localhost:9002/db"/>
+            <param name="url" value="jdbc:h2:${rep.home}/../db"/>
             <param name="schemaObjectPrefix" value="journal_"/>
             <param name="databaseType" value="h2"/>
             <param name="user" value="sa"/>
diff --git a/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/cluster/repository-with-test-journal.xml b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/cluster/repository-with-test-journal.xml
new file mode 100644
index 0000000..e8f10de
--- /dev/null
+++ b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/cluster/repository-with-test-journal.xml
@@ -0,0 +1,164 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
+                            "http://jackrabbit.apache.org/dtd/repository-1.6.dtd">
+<!-- Example Repository Configuration File
+     Used by
+     - org.apache.jackrabbit.core.config.RepositoryConfigTest.java
+     -
+-->
+<Repository>
+    <!--
+        virtual file system where the repository stores global state
+        (e.g. registered namespaces, custom node types, etc.)
+    -->
+    <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+        <param name="url" value="jdbc:h2:${rep.home}/../db"/>
+        <param name="schemaObjectPrefix" value="fs_"/>
+        <param name="user" value="sa"/>
+        <param name="password" value="sa"/>
+    </FileSystem>
+
+    <!--
+        data store configuration
+    -->
+    <DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
+        <param name="url" value="jdbc:h2:${rep.home}/../db"/>
+        <param name="schemaObjectPrefix" value="datastore_"/>
+        <param name="user" value="sa"/>
+        <param name="password" value="sa"/>
+    </DataStore>
+
+    <!--
+        security configuration
+    -->
+    <Security appName="Jackrabbit">
+        <!--
+            security manager:
+            class: FQN of class implementing the JackrabbitSecurityManager interface
+        -->
+        <SecurityManager class="org.apache.jackrabbit.core.security.simple.SimpleSecurityManager" workspaceName="security">
+            <!--
+            workspace access:
+            class: FQN of class implementing the WorkspaceAccessManager interface
+            -->
+            <!-- <WorkspaceAccessManager class="..."/> -->
+            <!-- <param name="config" value="${rep.home}/security.xml"/> -->
+        </SecurityManager>
+
+        <!--
+            access manager:
+            class: FQN of class implementing the AccessManager interface
+        -->
+        <AccessManager class="org.apache.jackrabbit.core.security.simple.SimpleAccessManager">
+            <!-- <param name="config" value="${rep.home}/access.xml"/> -->
+        </AccessManager>
+
+        <LoginModule class="org.apache.jackrabbit.core.security.simple.SimpleLoginModule">
+           <!-- 
+              anonymous user name ('anonymous' is the default value)
+            -->
+           <param name="anonymousId" value="anonymous"/>
+           <!--
+              administrator user id (default value if param is missing is 'admin')
+            -->
+           <param name="adminId" value="admin"/>
+        </LoginModule>
+    </Security>
+
+    <!--
+        location of workspaces root directory and name of default workspace
+    -->
+    <Workspaces rootPath="${rep.home}/workspaces" defaultWorkspace="default"/>
+    <!--
+        workspace configuration template:
+        used to create the initial workspace if there's no workspace yet
+    -->
+    <Workspace name="${wsp.name}">
+        <!--
+            virtual file system of the workspace:
+            class: FQN of class implementing the FileSystem interface
+        -->
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${wsp.home}"/>
+        </FileSystem>
+        
+        <!--
+            persistence manager of the workspace:
+            class: FQN of class implementing the PersistenceManager interface
+        -->
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
+            <param name="url" value="jdbc:h2:${rep.home}/../db"/>
+            <param name="schemaObjectPrefix" value="ws_${wsp.name}_"/>
+            <param name="user" value="sa"/>
+            <param name="password" value="sa"/>
+        </PersistenceManager>
+        
+        <!--
+            Search index and the file system it uses.
+            class: FQN of class implementing the QueryHandler interface
+        -->
+        <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+            <param name="path" value="${wsp.home}/index"/>
+            <param name="supportHighlighting" value="true"/>            
+        </SearchIndex>
+    </Workspace>
+
+    <!--
+        Configures the versioning
+    -->
+    <Versioning rootPath="${rep.home}/version">
+        <!--
+            Configures the filesystem to use for versioning for the respective
+            persistence manager
+        -->
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${rep.home}/version" />
+        </FileSystem>
+
+        <!--
+            Configures the persistence manager to be used for persisting version state.
+            Please note that the current versioning implementation is based on
+            a 'normal' persistence manager, but this could change in future
+            implementations.
+        -->
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
+            <param name="url" value="jdbc:h2:${rep.home}/../db"/>
+            <param name="schemaObjectPrefix" value="version_"/>
+            <param name="user" value="sa"/>
+            <param name="password" value="sa"/>
+        </PersistenceManager>
+    </Versioning>
+
+    <!--
+        Search index for content that is shared repository wide
+        (/jcr:system tree, contains mainly versions)
+    -->
+    <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+        <param name="path" value="${rep.home}/repository/index"/>
+        <param name="supportHighlighting" value="true"/>
+    </SearchIndex>
+    
+    <!--
+        Cluster configuration with system variables.
+    -->
+    <Cluster>
+        <Journal class="org.apache.jackrabbit.core.cluster.TestJournal"/>
+    </Cluster>
+    
+</Repository>
diff --git a/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/config/workspace.xml b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/config/workspace.xml
index b13ddf2..6f96ce6 100644
--- a/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/config/workspace.xml
+++ b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/config/workspace.xml
@@ -1,45 +1,45 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
--->
-<!DOCTYPE Workspace PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
-                           "http://jackrabbit.apache.org/dtd/repository-1.6.dtd">
-<Workspace name="default">
-  <!--
-      virtual file system of the workspace:
-      class: FQN of class implementing FileSystem interface
-  -->
-  <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
-    <param name="path" value="${wsp.home}"/>
-  </FileSystem>
-  <!--
-      persistence of the workspace:
-      class: FQN of class implementing PersistenceManager interface
-  -->
-  <PersistenceManager class="org.apache.jackrabbit.core.persistence.obj.ObjectPersistenceManager"/>
-  <!--
-      Search index and the file system it uses.
-  -->
-  <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-    <param name="useCompoundFile" value="true" />
-    <param name="minMergeDocs" value="1000" />
-    <param name="maxMergeDocs" value="10000" />
-    <param name="mergeFactor" value="10" />
-    <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
-      <param name="path" value="${wsp.home}/index" />
-    </FileSystem>
-  </SearchIndex>
-</Workspace>
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!DOCTYPE Workspace PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
+                           "http://jackrabbit.apache.org/dtd/repository-1.6.dtd">
+<Workspace name="default">
+  <!--
+      virtual file system of the workspace:
+      class: FQN of class implementing FileSystem interface
+  -->
+  <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+    <param name="path" value="${wsp.home}"/>
+  </FileSystem>
+  <!--
+      persistence of the workspace:
+      class: FQN of class implementing PersistenceManager interface
+  -->
+  <PersistenceManager class="org.apache.jackrabbit.core.persistence.obj.ObjectPersistenceManager"/>
+  <!--
+      Search index and the file system it uses.
+  -->
+  <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+    <param name="useCompoundFile" value="true" />
+    <param name="minMergeDocs" value="1000" />
+    <param name="maxMergeDocs" value="10000" />
+    <param name="mergeFactor" value="10" />
+    <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+      <param name="path" value="${wsp.home}/index" />
+    </FileSystem>
+  </SearchIndex>
+</Workspace>
diff --git a/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/integration/repository-with-SimpleFSDirectory.xml b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/integration/repository-with-SimpleFSDirectory.xml
new file mode 100644
index 0000000..9e1b5df
--- /dev/null
+++ b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/integration/repository-with-SimpleFSDirectory.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
+                            "http://jackrabbit.apache.org/dtd/repository-1.6.dtd">
+<Repository>
+    <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+        <param name="path" value="${rep.home}/repository"/>
+    </FileSystem>
+
+    <DataStore class="org.apache.jackrabbit.core.data.FileDataStore"/>
+    
+    <Security appName="Jackrabbit">
+        <SecurityManager class="org.apache.jackrabbit.core.DefaultSecurityManager" workspaceName="security"/>
+        <AccessManager class="org.apache.jackrabbit.core.security.DefaultAccessManager"/>
+
+        <LoginModule class="org.apache.jackrabbit.core.security.authentication.DefaultLoginModule">
+           <param name="anonymousId" value="anonymous"/>
+           <param name="adminId" value="admin"/>
+        </LoginModule>
+    </Security>
+
+    <Workspaces rootPath="${rep.home}/workspaces" defaultWorkspace="default"/>
+
+    <Workspace name="${wsp.name}">
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${wsp.home}"/>
+        </FileSystem>
+
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+          <param name="url" value="jdbc:derby:${wsp.home}/db;create=true"/>
+          <param name="schemaObjectPrefix" value="${wsp.name}_"/>
+        </PersistenceManager>
+
+        <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+            <param name="path" value="${wsp.home}/index"/>
+            <param name="useSimpleFSDirectory" value="true"/>
+        </SearchIndex>
+    </Workspace>
+
+    <Versioning rootPath="${rep.home}/version">
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${rep.home}/version" />
+        </FileSystem>
+
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+          <param name="url" value="jdbc:derby:${rep.home}/version/db;create=true"/>
+          <param name="schemaObjectPrefix" value="version_"/>
+        </PersistenceManager>
+    </Versioning>
+
+    <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+        <param name="path" value="${rep.home}/repository/index"/>
+        <param name="useSimpleFSDirectory" value="true"/>
+    </SearchIndex>
+    
+    <Cluster id="node1">
+        <Journal class="org.apache.jackrabbit.core.journal.MemoryJournal"/>
+    </Cluster>
+</Repository>
diff --git a/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_ns_cnd_nodetypes.cnd b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_ns_cnd_nodetypes.cnd
index 41b2ed7..ed705bb 100644
--- a/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_ns_cnd_nodetypes.cnd
+++ b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_ns_cnd_nodetypes.cnd
@@ -15,8 +15,8 @@
  * limitations under the License.
  */
 
-<testns3 = "test-namespace3">
-<testns4 = "test-namespace4">
+<testns3 = "http://ns.example.org/test-namespace3">
+<testns4 = "http://ns.example.org/test-namespace4">
 
 [testns3:emptyNodeType] > nt:base 
 
diff --git a/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_ns_xml_nodetypes.xml b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_ns_xml_nodetypes.xml
index dfb365b..ab56701 100644
--- a/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_ns_xml_nodetypes.xml
+++ b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_ns_xml_nodetypes.xml
@@ -17,8 +17,8 @@
  * limitations under the License.
  */
  -->
-<nodeTypes xmlns:testns1="test-namespace1"
-           xmlns:testns2="test-namespace2"
+<nodeTypes xmlns:testns1="http://ns.example.org/test-namespace1"
+           xmlns:testns2="http://ns.example.org/test-namespace2"
            xmlns:nt="http://www.jcp.org/jcr/nt/1.0">
 
   <nodeType name="testns2:emptyNodeType"
diff --git a/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_same_nt_name_cnd_nodetypes.cnd b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_same_nt_name_cnd_nodetypes.cnd
index ef0015c..8f6566b 100644
--- a/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_same_nt_name_cnd_nodetypes.cnd
+++ b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_same_nt_name_cnd_nodetypes.cnd
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-<testns6 = "test-namespace6">
+<testns6 = "http://ns.example.org/test-namespace6">
 
 [testns6:sameNodeType] > nt:base
 
diff --git a/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_same_nt_name_xml_nodetypes.xml b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_same_nt_name_xml_nodetypes.xml
index cfff27b..e63f2d6 100644
--- a/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_same_nt_name_xml_nodetypes.xml
+++ b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/nodetype/xml/test_same_nt_name_xml_nodetypes.xml
@@ -17,7 +17,7 @@
  * limitations under the License.
  */
  -->
-<nodeTypes xmlns:testns5="test-namespace5"
+<nodeTypes xmlns:testns5="http://ns.example.org/test-namespace5"
            xmlns:nt="http://www.jcp.org/jcr/nt/1.0">
 
   <nodeType name="testns5:sameNodeType"
diff --git a/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/query/test.rtf b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/query/test.rtf
new file mode 100644
index 0000000..e91b100
--- /dev/null
+++ b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/query/test.rtf
@@ -0,0 +1,157 @@
+{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff31507\deff0\stshfdbch31506\stshfloch31506\stshfhich31506\stshfbi31507\deflang2055\deflangfe2055\themelang2055\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f34\fbidi \froman\fcharset1\fprq2{\*\panose 02040503050406030204}Cambria Math;}
+{\f37\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
+{\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhimajor\f31502\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria;}
+{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}
+{\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}
+{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f39\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f40\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
+{\f42\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f43\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f44\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f45\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
+{\f46\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f47\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f409\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\f410\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}
+{\f412\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\f413\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\f416\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
+{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
+{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
+{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
+{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
+{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}
+{\fhimajor\f31528\fbidi \froman\fcharset238\fprq2 Cambria CE;}{\fhimajor\f31529\fbidi \froman\fcharset204\fprq2 Cambria Cyr;}{\fhimajor\f31531\fbidi \froman\fcharset161\fprq2 Cambria Greek;}{\fhimajor\f31532\fbidi \froman\fcharset162\fprq2 Cambria Tur;}
+{\fhimajor\f31535\fbidi \froman\fcharset186\fprq2 Cambria Baltic;}{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}
+{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}
+{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}
+{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
+{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
+{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbminor\f31558\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}
+{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}
+{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}
+{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}
+{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}
+{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}
+{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}
+{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;
+\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;
+\red192\green192\blue192;}{\*\defchp \f31506\fs22\lang2055\langfe1033\langfenp1033 }{\*\defpap \ql \li0\ri0\sa200\sl276\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{
+\ql \li0\ri0\sa200\sl276\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang2055\langfe1033\cgrid\langnp2055\langfenp1033 
+\snext0 \sqformat \spriority0 \styrsid15293873 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\*
+\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tscellwidthfts0\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa200\sl276\slmult1
+\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang2055\langfe1033\cgrid\langnp2055\langfenp1033 \snext11 \ssemihidden \sunhideused \sqformat Normal Table;}}
+{\*\rsidtbl \rsid11742246\rsid15293873}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author mreutegg}{\operator mreutegg}{\creatim\yr2008\mo10\dy23\hr10\min7}
+{\revtim\yr2008\mo10\dy23\hr10\min8}{\version1}{\edmins0}{\nofpages1}{\nofwords6}{\nofchars39}{\*\company Day Software AG}{\nofcharsws44}{\vern32895}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}}
+\paperw11906\paperh16838\margl1417\margr1417\margt1417\margb1134\gutter0\ltrsect 
+\deftab708\widowctrl\ftnbj\aenddoc\hyphhotz425\trackmoves1\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0
+\showxmlerrors1\noxlattoyen\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin1417\dgvorigin1417\dghshow1\dgvshow1
+\jexpand\viewkind1\viewscale140\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct
+\asianbrkrule\rsidroot11742246\newtblstyruls\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat \fet0
+{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\headery708\footery708\colsx708\endnhere\sectlinegrid360\sectdefaultcl\sectrsid15293873\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}
+{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}
+{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9
+\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar\ql \li0\ri0\sa200\sl276\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 
+\f31506\fs22\lang2055\langfe1033\cgrid\langnp2055\langfenp1033 {\rtlch\fcs1 \af31507 \ltrch\fcs0 \lang1033\langfe1033\langnp1033\insrsid11742246\charrsid11742246 The quick brown fox jumps over the lazy dog.}{\rtlch\fcs1 \af31507 \ltrch\fcs0 
+\lang1033\langfe1033\langnp1033\insrsid15293873\charrsid11742246 
+\par }{\*\themedata 504b030414000600080000002100828abc13fa0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb6ac3301045f785fe83d0b6d8
+72ba28a5d8cea249777d2cd20f18e4b12d6a8f843409c9df77ecb850ba082d74231062ce997b55ae8fe3a00e1893f354e9555e6885647de3a8abf4fbee29bbd7
+2a3150038327acf409935ed7d757e5ee14302999a654e99e393c18936c8f23a4dc072479697d1c81e51a3b13c07e4087e6b628ee8cf5c4489cf1c4d075f92a0b
+44d7a07a83c82f308ac7b0a0f0fbf90c2480980b58abc733615aa2d210c2e02cb04430076a7ee833dfb6ce62e3ed7e14693e8317d8cd0433bf5c60f53fea2fe7
+065bd80facb647e9e25c7fc421fd2ddb526b2e9373fed4bb902e182e97b7b461e6bfad3f010000ffff0300504b030414000600080000002100a5d6a7e7c00000
+00360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4fc7060abb08
+84a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b63095120f88d94fbc
+52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462a1a82fe353
+bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f7468656d652f7468
+656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b4b0d592c9c
+070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b4757e8d3f7
+29e245eb2b260a0238fd010000ffff0300504b03041400060008000000210096b5ade296060000501b0000160000007468656d652f7468656d652f7468656d65
+312e786d6cec594f6fdb3614bf0fd87720746f6327761a07758ad8b19b2d4d1bc46e871e698996d850a240d2497d1bdae38001c3ba618715d86d87615b8116d8
+a5fb34d93a6c1dd0afb0475292c5585e9236d88aad3e2412f9e3fbff1e1fa9abd7eec70c1d1221294fda5efd72cd4324f1794093b0eddd1ef62fad79482a9c04
+98f184b4bd2991deb58df7dfbb8ad755446282607d22d771db8b944ad79796a40fc3585ee62949606ecc458c15bc8a702910f808e8c66c69b9565b5d8a314d3c
+94e018c8de1a8fa94fd05093f43672e23d06af89927ac06762a049136785c10607758d9053d965021d62d6f6804fc08f86e4bef210c352c144dbab999fb7b471
+7509af678b985ab0b6b4ae6f7ed9ba6c4170b06c788a705430adf71bad2b5b057d03606a1ed7ebf5babd7a41cf00b0ef83a6569632cd467faddec9699640f671
+9e76b7d6ac355c7c89feca9cccad4ea7d36c65b258a206641f1b73f8b5da6a6373d9c11b90c537e7f08dce66b7bbeae00dc8e257e7f0fd2badd5868b37a088d1
+e4600ead1ddaef67d40bc898b3ed4af81ac0d76a197c86826828a24bb318f3442d8ab518dfe3a20f000d6458d104a9694ac6d88728eee2782428d60cf03ac1a5
+193be4cbb921cd0b495fd054b5bd0f530c1931a3f7eaf9f7af9e3f45c70f9e1d3ff8e9f8e1c3e3073f5a42ceaa6d9c84e5552fbffdeccfc71fa33f9e7ef3f2d1
+17d57859c6fffac327bffcfc793510d26726ce8b2f9ffcf6ecc98baf3efdfdbb4715f04d814765f890c644a29be408edf3181433567125272371be15c308d3f2
+8acd249438c19a4b05fd9e8a1cf4cd296699771c393ac4b5e01d01e5a30a787d72cf1178108989a2159c77a2d801ee72ce3a5c545a6147f32a99793849c26ae6
+6252c6ed637c58c5bb8b13c7bfbd490a75330f4b47f16e441c31f7184e140e494214d273fc80900aedee52ead87597fa824b3e56e82e451d4c2b4d32a423279a
+668bb6690c7e9956e90cfe766cb37b077538abd27a8b1cba48c80acc2a841f12e698f13a9e281c57911ce298950d7e03aba84ac8c154f8655c4f2af074481847
+bd804859b5e696007d4b4edfc150b12addbecba6b18b148a1e54d1bc81392f23b7f84137c2715a851dd0242a633f900710a218ed715505dfe56e86e877f0034e
+16bafb0e258ebb4faf06b769e888340b103d3311da9750aa9d0a1cd3e4efca31a3508f6d0c5c5c398602f8e2ebc71591f5b616e24dd893aa3261fb44f95d843b
+5974bb5c04f4edafb95b7892ec1108f3f98de75dc97d5772bdff7cc95d94cf672db4b3da0a6557f70db629362d72bcb0431e53c6066acac80d699a6409fb44d0
+8741bdce9c0e4971624a2378cceaba830b05366b90e0ea23aaa241845368b0eb9e2612ca8c742851ca251ceccc70256d8d87265dd96361531f186c3d9058edf2
+c00eafe8e1fc5c509031bb4d680e9f39a3154de0accc56ae644441edd76156d7429d995bdd88664a9dc3ad50197c38af1a0c16d684060441db02565e85f3b966
+0d0713cc48a0ed6ef7dedc2dc60b17e92219e180643ed27acffba86e9c94c78ab90980d8a9f0913ee49d62b512b79626fb06dccee2a432bbc60276b9f7dec44b
+7904cfbca4f3f6443ab2a49c9c2c41476dafd55c6e7ac8c769db1bc399161ee314bc2e75cf8759081743be1236ec4f4d6693e5336fb672c5dc24a8c33585b5fb
+9cc24e1d4885545b58463634cc5416022cd19cacfccb4d30eb45296023fd35a458598360f8d7a4003bbaae25e331f155d9d9a5116d3bfb9a95523e51440ca2e0
+088dd844ec6370bf0e55d027a012ae264c45d02f708fa6ad6da6dce29c255df9f6cae0ec38666984b372ab5334cf640b37795cc860de4ae2816e95b21be5ceaf
+8a49f90b52a51cc6ff3355f47e0237052b81f6800fd7b802239daf6d8f0b1571a8426944fdbe80c6c1d40e8816b88b8569082ab84c36ff0539d4ff6dce591a26
+ade1c0a7f669880485fd484582903d284b26fa4e2156cff62e4b9265844c4495c495a9157b440e091bea1ab8aaf7760f4510eaa69a6465c0e04ec69ffb9e65d0
+28d44d4e39df9c1a52ecbd3607fee9cec7263328e5d661d3d0e4f62f44acd855ed7ab33cdf7bcb8ae889599bd5c8b3029895b6825696f6af29c239b75a5bb1e6
+345e6ee6c28117e73586c1a2214ae1be07e93fb0ff51e133fb65426fa843be0fb515c187064d0cc206a2fa926d3c902e907670048d931db4c1a44959d366ad93
+b65abe595f70a75bf03d616c2dd959fc7d4e6317cd99cbcec9c58b34766661c7d6766ca1a9c1b327531486c6f941c638c67cd22a7f75e2a37be0e82db8df9f30
+254d30c1372581a1f51c983c80e4b71ccdd28dbf000000ffff0300504b0304140006000800000021000dd1909fb60000001b010000270000007468656d652f74
+68656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277086f6fd3ba109126dd88d0add40384e4350d363f24
+51eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64b060828e6f37ed1567914b284d262452282e3198
+720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996509affb3fd381a89672f1f165dfe514173d9850528
+a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100828abc13fa0000001c0200001300000000000000000000000000
+000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0000000360100000b000000000000000000000000
+002b0100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000001c00000000000000000000000000140200007468
+656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d001400060008000000210096b5ade296060000501b000016000000000000000000
+00000000d10200007468656d652f7468656d652f7468656d65312e786d6c504b01022d00140006000800000021000dd1909fb60000001b010000270000000000
+00000000000000009b0900007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000960a00000000}
+{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d
+617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169
+6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363
+656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e}
+{\*\latentstyles\lsdstimax267\lsdlockeddef0\lsdsemihiddendef1\lsdunhideuseddef1\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;
+\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4;
+\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;
+\lsdpriority39 \lsdlocked0 toc 1;\lsdpriority39 \lsdlocked0 toc 2;\lsdpriority39 \lsdlocked0 toc 3;\lsdpriority39 \lsdlocked0 toc 4;\lsdpriority39 \lsdlocked0 toc 5;\lsdpriority39 \lsdlocked0 toc 6;\lsdpriority39 \lsdlocked0 toc 7;
+\lsdpriority39 \lsdlocked0 toc 8;\lsdpriority39 \lsdlocked0 toc 9;\lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdpriority1 \lsdlocked0 Default Paragraph Font;
+\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority22 \lsdlocked0 Strong;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority59 \lsdlocked0 Table Grid;\lsdunhideused0 \lsdlocked0 Placeholder Text;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading;\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List;\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List;\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 1;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;\lsdunhideused0 \lsdlocked0 Revision;
+\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 1;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 2;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 2;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 2;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 3;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 3;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 4;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 4;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 4;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 5;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 5;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 5;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 6;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 6;
+\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;
+\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis;
+\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;
+\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdpriority37 \lsdlocked0 Bibliography;\lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;}}{\*\datastore 010500000200000018000000
+4d73786d6c322e534158584d4c5265616465722e352e3000000000000000000000060000
+d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffffec69d9888b8b3d4c859eaf6cd158be0f0000000000000000000000003068
+2a89e634c901feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000
+000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000
+0000000000000000000000000000000000000000000000000105000000000000}}
\ No newline at end of file
diff --git a/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/security/simple/simple_repository.xml b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/security/simple/simple_repository.xml
new file mode 100644
index 0000000..8c12f98
--- /dev/null
+++ b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/security/simple/simple_repository.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 2.4//EN"
+                            "http://jackrabbit.apache.org/dtd/repository-2.4.dtd">
+<Repository>
+    <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+        <param name="path" value="${rep.home}/repository"/>
+    </FileSystem>
+
+    <Security appName="Jackrabbit">
+        <SecurityManager class="org.apache.jackrabbit.core.security.simple.SimpleSecurityManager">
+        </SecurityManager>
+
+        <AccessManager class="org.apache.jackrabbit.core.security.simple.SimpleAccessManager">
+        </AccessManager>
+
+        <LoginModule class="org.apache.jackrabbit.core.security.simple.SimpleLoginModule">
+        </LoginModule>
+    </Security>
+
+    <!--
+        location of workspaces root directory and name of default workspace
+    -->
+    <Workspaces rootPath="${rep.home}/workspaces" defaultWorkspace="default"/>
+    <!--
+        workspace configuration template:
+        used to create the initial workspace if there's no workspace yet
+    -->
+    <Workspace name="${wsp.name}">
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${wsp.home}"/>
+        </FileSystem>
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+          <param name="url" value="jdbc:derby:${wsp.home}/db;create=true"/>
+          <param name="schemaObjectPrefix" value="${wsp.name}_"/>
+        </PersistenceManager>
+        <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+            <param name="path" value="${wsp.home}/index"/>
+        </SearchIndex>
+    </Workspace>
+
+    <!--
+        Configures the versioning
+    -->
+    <Versioning rootPath="${rep.home}/version">
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${rep.home}/version" />
+        </FileSystem>
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+          <param name="url" value="jdbc:derby:${rep.home}/version/db;create=true"/>
+          <param name="schemaObjectPrefix" value="version_"/>
+        </PersistenceManager>
+    </Versioning>
+
+    <!--
+        Search index for content that is shared repository wide
+        (/jcr:system tree, contains mainly versions)
+    -->
+    <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+        <param name="path" value="${rep.home}/repository/index"/>
+    </SearchIndex>
+
+</Repository>
diff --git a/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/security/user/repository.xml b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/security/user/repository.xml
new file mode 100644
index 0000000..2756b20
--- /dev/null
+++ b/jackrabbit-core/src/test/resources/org/apache/jackrabbit/core/security/user/repository.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 2.4//EN"
+                            "http://jackrabbit.apache.org/dtd/repository-2.4.dtd">
+<Repository>
+    <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+        <param name="path" value="${rep.home}/repository"/>
+    </FileSystem>
+
+    <Security appName="Jackrabbit">
+        <SecurityManager class="org.apache.jackrabbit.core.UserPerWorkspaceSecurityManager">
+            <UserManager class="org.apache.jackrabbit.core.security.user.UserPerWorkspaceUserManager">
+                <param name="usersPath" value="/home/users"/>
+                <param name="groupsPath" value="/home/groups"/>
+                <param name="defaultDepth" value="1"/>
+                <param name="autoExpandTree" value="true"/>
+                <AuthorizableAction class="org.apache.jackrabbit.core.security.user.action.AccessControlAction">
+                  <param name="groupPrivilegeNames" value="jcr:read"/>
+                  <param name="userPrivilegeNames" value="jcr:all"/>
+                </AuthorizableAction>
+            </UserManager>
+        </SecurityManager>
+
+        <AccessManager class="org.apache.jackrabbit.core.security.DefaultAccessManager">
+        </AccessManager>
+
+        <LoginModule class="org.apache.jackrabbit.core.security.authentication.DefaultLoginModule">
+           <param name="anonymousId" value="anonymous"/>
+           <param name="adminId" value="admin"/>
+        </LoginModule>
+    </Security>
+
+    <!--
+        location of workspaces root directory and name of default workspace
+    -->
+    <Workspaces rootPath="${rep.home}/workspaces" defaultWorkspace="default"/>
+    <!--
+        workspace configuration template:
+        used to create the initial workspace if there's no workspace yet
+    -->
+    <Workspace name="${wsp.name}">
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${wsp.home}"/>
+        </FileSystem>
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+          <param name="url" value="jdbc:derby:${wsp.home}/db;create=true"/>
+          <param name="schemaObjectPrefix" value="${wsp.name}_"/>
+        </PersistenceManager>
+        <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+            <param name="path" value="${wsp.home}/index"/>
+        </SearchIndex>
+    </Workspace>
+
+    <!--
+        Configures the versioning
+    -->
+    <Versioning rootPath="${rep.home}/version">
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${rep.home}/version" />
+        </FileSystem>
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+          <param name="url" value="jdbc:derby:${rep.home}/version/db;create=true"/>
+          <param name="schemaObjectPrefix" value="version_"/>
+        </PersistenceManager>
+    </Versioning>
+
+    <!--
+        Search index for content that is shared repository wide
+        (/jcr:system tree, contains mainly versions)
+    -->
+    <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+        <param name="path" value="${rep.home}/repository/index"/>
+    </SearchIndex>
+    
+</Repository>
diff --git a/jackrabbit-data/pom.xml b/jackrabbit-data/pom.xml
new file mode 100644
index 0000000..b7cdfea
--- /dev/null
+++ b/jackrabbit-data/pom.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor 
+	license agreements. See the NOTICE file distributed with this work for additional 
+	information regarding copyright ownership. The ASF licenses this file to 
+	You under the Apache License, Version 2.0 (the "License"); you may not use 
+	this file except in compliance with the License. You may obtain a copy of 
+	the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required 
+	by applicable law or agreed to in writing, software distributed under the 
+	License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 
+	OF ANY KIND, either express or implied. See the License for the specific 
+	language governing permissions and limitations under the License. -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd ">
+	<modelVersion>4.0.0</modelVersion>
+
+	<!-- ====================================================================== -->
+	<!-- P R O J E C T D E S C R I P T I O N -->
+	<!-- ====================================================================== -->
+	<parent>
+		<groupId>org.apache.jackrabbit</groupId>
+		<artifactId>jackrabbit-parent</artifactId>
+		<version>2.10.1</version>
+		<relativePath>../jackrabbit-parent/pom.xml</relativePath>
+	</parent>
+	<artifactId>jackrabbit-data</artifactId>
+	<name>Jackrabbit Data</name>
+	<description>Jackrabbit DataStore Implentations</description>
+	<packaging>bundle</packaging>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.felix</groupId>
+				<artifactId>maven-bundle-plugin</artifactId>
+				<extensions>true</extensions>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.rat</groupId>
+				<artifactId>apache-rat-plugin</artifactId>
+				<configuration>
+					<excludes>
+						<exclude>.checkstyle</exclude>
+					</excludes>
+				</configuration>
+			</plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>2.4</version>
+                <executions>
+                     <execution>
+                         <goals>
+                             <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+		</plugins>
+	</build>
+
+	<!-- ====================================================================== -->
+	<!-- D E P E N D E N C I E S -->
+	<!-- ====================================================================== -->
+	<dependencies>
+		<dependency>
+			<groupId>javax.jcr</groupId>
+			<artifactId>jcr</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.jackrabbit</groupId>
+			<artifactId>jackrabbit-api</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.jackrabbit</groupId>
+			<artifactId>jackrabbit-jcr-commons</artifactId>
+			<version>${project.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-dbcp</groupId>
+			<artifactId>commons-dbcp</artifactId>
+			<version>1.3</version>
+      <optional>true</optional>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.derby</groupId>
+			<artifactId>derby</artifactId>
+      <optional>true</optional>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>jcl-over-slf4j</artifactId>
+		</dependency>
+
+    <!-- Test dependencies -->
+    <dependency>
+        <groupId>junit</groupId>
+        <artifactId>junit</artifactId>
+        <scope>test</scope>
+    </dependency>
+    <dependency>
+        <groupId>org.slf4j</groupId>
+        <artifactId>slf4j-log4j12</artifactId>
+        <version>1.7.5</version>
+        <scope>test</scope>
+    </dependency>
+	</dependencies>
+</project>
\ No newline at end of file
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ConfigurationException.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/config/ConfigurationException.java
similarity index 100%
rename from jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/ConfigurationException.java
rename to jackrabbit-data/src/main/java/org/apache/jackrabbit/core/config/ConfigurationException.java
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/DataSourceConfig.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/config/DataSourceConfig.java
similarity index 100%
rename from jackrabbit-core/src/main/java/org/apache/jackrabbit/core/config/DataSourceConfig.java
rename to jackrabbit-data/src/main/java/org/apache/jackrabbit/core/config/DataSourceConfig.java
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AbstractDataRecord.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AbstractDataRecord.java
new file mode 100644
index 0000000..3c7f7d9
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AbstractDataRecord.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.core.data;
+
+
+/**
+ * Abstract data record base class. This base class contains only
+ * a reference to the data identifier of the record and implements
+ * the standard {@link Object} equality, hash code, and string
+ * representation methods based on the identifier.
+ */
+public abstract class AbstractDataRecord implements DataRecord {
+
+    /**
+     * The data store that contains this record.
+     */
+    private final AbstractDataStore store;
+
+    /**
+     * The binary identifier;
+     */
+    private final DataIdentifier identifier;
+
+    /**
+     * Creates a data record with the given identifier.
+     *
+     * @param identifier data identifier
+     */
+    public AbstractDataRecord(
+            AbstractDataStore store, DataIdentifier identifier) {
+        this.store = store;
+        this.identifier = identifier;
+    }
+
+    /**
+     * Returns the data identifier.
+     *
+     * @return data identifier
+     */
+    public DataIdentifier getIdentifier() {
+        return identifier;
+    }
+
+    public String getReference() {
+        return store.getReferenceFromIdentifier(identifier);
+    }
+
+    /**
+     * Returns the string representation of the data identifier.
+     *
+     * @return string representation
+     */
+    public String toString() {
+        return identifier.toString();
+    }
+
+    /**
+     * Checks if the given object is a data record with the same identifier
+     * as this one.
+     *
+     * @param object other object
+     * @return <code>true</code> if the other object is a data record and has
+     *         the same identifier as this one, <code>false</code> otherwise
+     */
+    public boolean equals(Object object) {
+        return (object instanceof DataRecord)
+            && identifier.equals(((DataRecord) object).getIdentifier());
+    }
+
+    /**
+     * Returns the hash code of the data identifier.
+     *
+     * @return hash code
+     */
+    public int hashCode() {
+        return identifier.hashCode();
+    }
+
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AbstractDataStore.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AbstractDataStore.java
new file mode 100644
index 0000000..943dcf0
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AbstractDataStore.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.security.SecureRandom;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+
+public abstract class AbstractDataStore implements DataStore {
+
+    private static final String ALGORITHM = "HmacSHA1";
+
+    /**
+     * Array of hexadecimal digits.
+     */
+    private static final char[] HEX = "0123456789abcdef".toCharArray();
+
+    /**
+     * Cached copy of the reference key of this data store. Initialized in
+     * {@link #getReferenceKey()} when the key is first accessed.
+     */
+    private byte[] referenceKey = null;
+
+    //---------------------------------------------------------< DataStore >--
+
+    public DataRecord getRecord(DataIdentifier identifier)
+            throws DataStoreException {
+        DataRecord record = getRecordIfStored(identifier);
+        if (record != null) {
+            return record;
+        } else {
+            throw new DataStoreException(
+                    "Record " + identifier + " does not exist");
+        }
+    }
+
+    public DataRecord getRecordFromReference(String reference)
+            throws DataStoreException {
+        if (reference != null) {
+            int colon = reference.indexOf(':');
+            if (colon != -1) {
+                DataIdentifier identifier =
+                        new DataIdentifier(reference.substring(0, colon));
+                if (reference.equals(getReferenceFromIdentifier(identifier))) {
+                    return getRecordIfStored(identifier);
+                }
+            }
+        }
+        return null;
+    }
+
+    //---------------------------------------------------------< protected >--
+
+    /**
+     * Returns the hex encoding of the given bytes.
+     *
+     * @param value value to be encoded
+     * @return encoded value
+     */
+    protected static String encodeHexString(byte[] value) {
+        char[] buffer = new char[value.length * 2];
+        for (int i = 0; i < value.length; i++) {
+            buffer[2 * i] = HEX[(value[i] >> 4) & 0x0f];
+            buffer[2 * i + 1] = HEX[value[i] & 0x0f];
+        }
+        return new String(buffer);
+    }
+
+    protected String getReferenceFromIdentifier(DataIdentifier identifier) {
+        try {
+            String id = identifier.toString();
+
+            Mac mac = Mac.getInstance(ALGORITHM);
+            mac.init(new SecretKeySpec(getReferenceKey(), ALGORITHM));
+            byte[] hash = mac.doFinal(id.getBytes("UTF-8"));
+
+            return id + ':' + encodeHexString(hash);
+        } catch (Exception e) {
+            // TODO: log a warning about this exception
+        }
+        return null;
+    }
+
+    /**
+     * Returns the reference key of this data store. If one does not already
+     * exist, it is automatically created in an implementation-specific way.
+     * The default implementation simply creates a temporary random key that's
+     * valid only until the data store gets restarted. Subclasses can override
+     * and/or decorate this method to support a more persistent reference key.
+     * <p>
+     * This method is called only once during the lifetime of a data store
+     * instance and the return value is cached in memory, so it's no problem
+     * if the implementation is slow.
+     *
+     * @return reference key
+     * @throws DataStoreException if the key is not available
+     */
+    protected byte[] getOrCreateReferenceKey() throws DataStoreException {
+        byte[] referenceKeyValue = new byte[256];
+        new SecureRandom().nextBytes(referenceKeyValue);
+        return referenceKeyValue;
+    }
+
+    //-----------------------------------------------------------< private >--
+
+    /**
+     * Returns the reference key of this data store. Synchronized to
+     * control concurrent access to the cached {@link #referenceKey} value.
+     *
+     * @return reference key
+     * @throws DataStoreException if the key is not available
+     */
+    private synchronized byte[] getReferenceKey() throws DataStoreException {
+        if (referenceKey == null) {
+            referenceKey = getOrCreateReferenceKey();
+        }
+        return referenceKey;
+    }
+
+}
\ No newline at end of file
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AsyncTouchCallback.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AsyncTouchCallback.java
new file mode 100644
index 0000000..2084000
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AsyncTouchCallback.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.core.data;
+/**
+ * This interface defines callback methods to reflect the status of asynchronous
+ * touch.
+ */
+public interface AsyncTouchCallback {
+    
+    
+    /**
+     * Callback method for successful asynchronous touch.
+     */
+    public void onSuccess(AsyncTouchResult result);
+    
+    /**
+     * Callback method for failed asynchronous touch.
+     */
+    public void onFailure(AsyncTouchResult result);
+    
+    /**
+     * Callback method for aborted asynchronous touch.
+     */
+    public void onAbort(AsyncTouchResult result);
+
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AsyncTouchResult.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AsyncTouchResult.java
new file mode 100644
index 0000000..014db65
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AsyncTouchResult.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+/**
+ * 
+ * The class holds the result of asynchronous touch to {@link Backend}
+ */
+public class AsyncTouchResult {
+    /**
+     * {@link DataIdentifier} on which asynchronous touch is initiated.
+     */
+    private final DataIdentifier identifier;
+    /**
+     * Any {@link Exception} which is raised in asynchronously touch.
+     */
+    private Exception exception;
+    
+    public AsyncTouchResult(DataIdentifier identifier) {
+        super();
+        this.identifier = identifier;
+    }
+
+    public DataIdentifier getIdentifier() {
+        return identifier;
+    }
+
+    public Exception getException() {
+        return exception;
+    }
+
+    public void setException(Exception exception) {
+        this.exception = exception;
+    }
+
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AsyncUploadCache.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AsyncUploadCache.java
new file mode 100644
index 0000000..4cb2ac5
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AsyncUploadCache.java
@@ -0,0 +1,352 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInput;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class holds all in progress uploads. This class contains two data
+ * structures, one is {@link #asyncUploadMap} which is {@link Map<String, Long>}
+ * of file path vs lastModified of upload. The second {@link #toBeDeleted} is
+ * {@link Set<String>} of upload which is marked for delete, while it is already
+ * in progress. Before starting an asynchronous upload, it requires to invoke
+ * {@link #add(String)} to add entry to {@link #asyncUploadMap}. After
+ * asynchronous upload completes, it requires to invoke
+ * {@link #remove(String, AsyncUploadCacheResult)} to remove entry from
+ * {@link #asyncUploadMap} Any modification to this class are immediately
+ * persisted to local file system. {@link #asyncUploadMap} is persisted to /
+ * {@link homeDir}/ {@link #PENDIND_UPLOAD_FILE}. {@link #toBeDeleted} is
+ * persisted to / {@link homeDir}/ {@link #TO_BE_DELETED_UPLOAD_FILE}. The /
+ * {@link homeDir} refer to ${rep.home}.
+ */
+public class AsyncUploadCache {
+    private static final Logger LOG = LoggerFactory.getLogger(AsyncUploadCache.class);
+
+    /**
+     * {@link Map} of fileName Vs lastModified to store asynchronous upload.
+     */
+    Map<String, Long> asyncUploadMap = new HashMap<String, Long>();
+
+    /**
+     * {@link Set} of fileName which are mark for delete during asynchronous
+     * Upload.
+     */
+    Set<String> toBeDeleted = new HashSet<String>();
+
+    String path;
+
+    String homeDir;
+
+    int asyncUploadLimit;
+
+    private File pendingUploads;
+
+    private File toBeDeletedUploads;
+
+    private static final String PENDIND_UPLOAD_FILE = "async-pending-uploads.ser";
+
+    private static final String TO_BE_DELETED_UPLOAD_FILE = "async-tobedeleted-uploads.ser";
+
+    /**
+     * This methods checks if file can be added to {@link #asyncUploadMap}. If
+     * yes it adds to {@link #asyncUploadMap} and
+     * {@link #serializeAsyncUploadMap()} the {@link #asyncUploadMap} to disk.
+     * 
+     * @return {@link AsyncUploadCacheResult} if successfully added to
+     *         asynchronous uploads it sets
+     *         {@link AsyncUploadCacheResult#setAsyncUpload(boolean)} to true
+     *         else sets to false.
+     */
+    public synchronized AsyncUploadCacheResult add(String fileName)
+            throws IOException {
+        AsyncUploadCacheResult result = new AsyncUploadCacheResult();
+        if (asyncUploadMap.entrySet().size() >= asyncUploadLimit) {
+            LOG.info(
+                "Async write limit [{}]  reached. File [{}] not added to async write cache.",
+                asyncUploadLimit, fileName);
+            LOG.debug("AsyncUploadCache size=[{}] and entries =[{}]",
+                asyncUploadMap.size(), asyncUploadMap.keySet());
+            result.setAsyncUpload(false);
+        } else {
+            long startTime = System.currentTimeMillis();
+            if (toBeDeleted.remove(fileName)) {
+                serializeToBeDeleted();
+            }
+            asyncUploadMap.put(fileName, System.currentTimeMillis());
+            serializeAsyncUploadMap();
+            LOG.debug("added file [{}] to asyncUploadMap upoad took [{}] sec",
+                fileName, ((System.currentTimeMillis() - startTime) / 1000));
+            LOG.debug("AsyncUploadCache size=[{}] and entries =[{}]",
+                asyncUploadMap.size(), asyncUploadMap.keySet());
+            result.setAsyncUpload(true);
+        }
+        return result;
+    }
+
+    /**
+     * This methods removes file (if found) from {@link #asyncUploadMap}. If
+     * file is found, it immediately serializes the {@link #asyncUploadMap} to
+     * disk. This method sets
+     * {@link AsyncUploadCacheResult#setRequiresDelete(boolean)} to true, if
+     * asynchronous upload found to be in {@link #toBeDeleted} set i.e. marked
+     * for delete.
+     */
+    public synchronized AsyncUploadCacheResult remove(String fileName)
+            throws IOException {
+        long startTime = System.currentTimeMillis();
+        Long retVal = asyncUploadMap.remove(fileName);
+        if (retVal != null) {
+            serializeAsyncUploadMap();
+            LOG.debug("removed file [{}] from asyncUploadMap took [{}] sec",
+                fileName, ((System.currentTimeMillis() - startTime) / 1000));
+            LOG.debug("AsyncUploadCache size=[{}] and entries =[{}]",
+                asyncUploadMap.size(), asyncUploadMap.keySet());
+        } else {
+            LOG.debug("cannot removed file [{}] from asyncUploadMap took [{}] sec. File not found.",
+                fileName, ((System.currentTimeMillis() - startTime) / 1000));
+            LOG.debug("AsyncUploadCache size=[{}] and entries =[{}]",
+                asyncUploadMap.size(), asyncUploadMap.keySet());
+        }
+        AsyncUploadCacheResult result = new AsyncUploadCacheResult();
+        result.setRequiresDelete(toBeDeleted.contains(fileName));
+        return result;
+    }
+
+    /**
+     * This methods returns the in progress asynchronous uploads which are not
+     * marked for delete.
+     */
+    public synchronized Set<String> getAll() {
+        Set<String> retVal = new HashSet<String>();
+        retVal.addAll(asyncUploadMap.keySet());
+        retVal.removeAll(toBeDeleted);
+        return retVal;
+    }
+
+    /**
+     * This methos checks if asynchronous upload is in progress for @param
+     * fileName. If @param touch is true, the lastModified is updated to current
+     * time.
+     */
+    public synchronized boolean hasEntry(String fileName, boolean touch)
+            throws IOException {
+        boolean contains = asyncUploadMap.containsKey(fileName)
+            && !toBeDeleted.contains(fileName);
+        if (touch && contains) {
+            long timeStamp = System.currentTimeMillis();
+            asyncUploadMap.put(fileName, timeStamp);
+            serializeAsyncUploadMap();
+        }
+        return contains;
+    }
+
+    /**
+     * Returns lastModified from {@link #asyncUploadMap} if found else returns
+     * 0.
+     */
+    public synchronized long getLastModified(String fileName) {
+        return asyncUploadMap.get(fileName) != null
+            && !toBeDeleted.contains(fileName)
+                ? asyncUploadMap.get(fileName)
+                : 0;
+    }
+
+    /**
+     * This methods deletes asynchronous upload for @param fileName if there
+     * exists asynchronous upload for @param fileName.
+     */
+    public synchronized void delete(String fileName) throws IOException {
+        boolean serialize = false;
+        if (toBeDeleted.remove(fileName)) {
+            serialize = true;
+        }
+        if (asyncUploadMap.containsKey(fileName) && toBeDeleted.add(fileName)) {
+            serialize = true;
+        }
+        if (serialize) {
+            serializeToBeDeleted();
+        }
+    }
+
+    /**
+     * Delete in progress asynchronous uploads which are older than @param min.
+     * This method leverage lastModified stored in {@link #asyncUploadMap}
+     */
+    public synchronized Set<String> deleteOlderThan(long min)
+            throws IOException {
+        min = min - 1000;
+        LOG.info("deleteOlderThan min [{}]", min);
+        Set<String> deleteSet = new HashSet<String>();
+        for (Map.Entry<String, Long> entry : asyncUploadMap.entrySet()) {
+            if (entry.getValue() < min) {
+                deleteSet.add(entry.getKey());
+            }
+        }
+        if (deleteSet.size() > 0) {
+            LOG.debug("deleteOlderThan set [{}]", deleteSet);
+            toBeDeleted.addAll(deleteSet);
+            serializeToBeDeleted();
+        }
+        return deleteSet;
+    }
+
+    /**
+     * @param homeDir
+     *            home directory of repository.
+     * @param path
+     *            path of the {@link LocalCache}
+     * @param asyncUploadLimit
+     *            the maximum number of asynchronous uploads
+     */
+    public synchronized void init(String homeDir, String path,
+            int asyncUploadLimit) throws IOException, ClassNotFoundException {
+        this.homeDir = homeDir;
+        this.path = path;
+        this.asyncUploadLimit = asyncUploadLimit;
+        LOG.info(
+            "AsynWriteCache:homeDir=[{}], path=[{}], asyncUploadLimit=[{}].",
+            new Object[] { homeDir, path, asyncUploadLimit });
+        pendingUploads = new File(homeDir + "/" + PENDIND_UPLOAD_FILE);
+        toBeDeletedUploads = new File(homeDir + "/" + TO_BE_DELETED_UPLOAD_FILE);
+        if (pendingUploads.exists()) {
+            deserializeAsyncUploadMap();
+        } else {
+            pendingUploads.createNewFile();
+            asyncUploadMap = new HashMap<String, Long>();
+            serializeAsyncUploadMap();
+        }
+        
+        if (toBeDeletedUploads.exists()) {
+            deserializeToBeDeleted();
+        } else {
+            toBeDeletedUploads.createNewFile();
+            asyncUploadMap = new HashMap<String, Long>();
+            serializeToBeDeleted();
+        }
+    }
+
+    /**
+     * Reset the {@link AsyncUploadCache} to empty {@link #asyncUploadMap} and
+     * {@link #toBeDeleted}
+     */
+    public synchronized void reset() throws IOException {
+        String filePath = pendingUploads.getAbsolutePath();
+        if (!pendingUploads.exists()) {
+            pendingUploads.createNewFile();
+        }
+        pendingUploads.createNewFile();
+        asyncUploadMap = new HashMap<String, Long>();
+        serializeAsyncUploadMap();
+
+        if (!toBeDeletedUploads.exists()) {
+            toBeDeletedUploads.createNewFile();
+        }
+        toBeDeletedUploads.createNewFile();
+        toBeDeleted = new HashSet<String>();
+        serializeToBeDeleted();
+    }
+
+    /**
+     * Serialize {@link #asyncUploadMap} to local file system.
+     */
+    private synchronized void serializeAsyncUploadMap() throws IOException {
+
+        // use buffering
+        OutputStream fos = new FileOutputStream(pendingUploads);
+        OutputStream buffer = new BufferedOutputStream(fos);
+        ObjectOutput output = new ObjectOutputStream(buffer);
+        try {
+            output.writeObject(asyncUploadMap);
+            output.flush();
+        } finally {
+            output.close();
+            IOUtils.closeQuietly(buffer);
+            
+        }
+    }
+
+    /**
+     * Deserialize {@link #asyncUploadMap} from local file system.
+     */
+    private synchronized void deserializeAsyncUploadMap() throws IOException,
+            ClassNotFoundException {
+        // use buffering
+        InputStream fis = new FileInputStream(pendingUploads);
+        InputStream buffer = new BufferedInputStream(fis);
+        ObjectInput input = new ObjectInputStream(buffer);
+        try {
+            asyncUploadMap = (Map<String, Long>) input.readObject();
+        } finally {
+            input.close();
+            IOUtils.closeQuietly(buffer);
+        }
+    }
+
+    /**
+     * Serialize {@link #toBeDeleted} to local file system.
+     */
+    private synchronized void serializeToBeDeleted() throws IOException {
+
+        // use buffering
+        OutputStream fos = new FileOutputStream(toBeDeletedUploads);
+        OutputStream buffer = new BufferedOutputStream(fos);
+        ObjectOutput output = new ObjectOutputStream(buffer);
+        try {
+            output.writeObject(toBeDeleted);
+            output.flush();
+        } finally {
+            output.close();
+            IOUtils.closeQuietly(buffer);
+        }
+    }
+
+    /**
+     * Deserialize {@link #toBeDeleted} from local file system.
+     */
+    private synchronized void deserializeToBeDeleted() throws IOException,
+            ClassNotFoundException {
+        // use buffering
+        InputStream fis = new FileInputStream(toBeDeletedUploads);
+        InputStream buffer = new BufferedInputStream(fis);
+        ObjectInput input = new ObjectInputStream(buffer);
+        try {
+            toBeDeleted = (Set<String>) input.readObject();
+        } finally {
+            input.close();
+            IOUtils.closeQuietly(buffer);
+        }
+    }
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AsyncUploadCacheResult.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AsyncUploadCacheResult.java
new file mode 100644
index 0000000..100e332
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AsyncUploadCacheResult.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.io.File;
+
+/**
+ * This class holds result of asynchronous upload from {@link AsyncUploadCache}
+ */
+public class AsyncUploadCacheResult {
+
+    /**
+     * flag to indicate that asynchronous upload can be started on file.
+     */
+    private boolean asyncUpload;
+
+    /**
+     * flag to indicate that cached file requires to be deleted. It is
+     * applicable in case where file marked for delete before asynchronous
+     * upload completes.
+     */
+    private boolean requiresDelete;
+
+    private File file;
+
+    /**
+     * Flag to denote that asynchronous upload can be started on file.
+     */
+    public boolean canAsyncUpload() {
+        return asyncUpload;
+    }
+
+    public void setAsyncUpload(boolean asyncUpload) {
+        this.asyncUpload = asyncUpload;
+    }
+
+    /**
+     * Flag to indicate that record to be deleted from {@link Datastore}.
+     */
+    public boolean doRequiresDelete() {
+        return requiresDelete;
+    }
+
+    public void setRequiresDelete(boolean requiresDelete) {
+        this.requiresDelete = requiresDelete;
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public void setFile(File file) {
+        this.file = file;
+    }
+
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AsyncUploadCallback.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AsyncUploadCallback.java
new file mode 100644
index 0000000..af18c03
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AsyncUploadCallback.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.core.data;
+
+/**
+ * This interface defines callback methods to reflect the status of asynchronous
+ * upload.
+ */
+public interface AsyncUploadCallback {
+    
+    /**
+     * Callback method for successful asynchronous upload.
+     */
+    public void onSuccess(AsyncUploadResult result);
+    
+    /**
+     * Callback method for failed asynchronous upload.
+     */
+    public void onFailure(AsyncUploadResult result);
+    
+    /**
+     * Callback method for aborted asynchronous upload.
+     */
+    public void onAbort(AsyncUploadResult result);
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AsyncUploadResult.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AsyncUploadResult.java
new file mode 100644
index 0000000..bdbbb4c
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/AsyncUploadResult.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.io.File;
+
+/**
+ * 
+ * The class holds the result of asynchronous upload to {@link Backend}
+ */
+public class AsyncUploadResult {
+    /**
+     * {@link DataIdentifier} on which asynchronous upload is initiated.
+     */
+    private final DataIdentifier identifier;
+    
+    /**
+     * {@link File} which is asynchronously uploaded.
+     */
+    private final File file;
+    
+    /**
+     * Any {@link Exception} which is raised in asynchronously upload.
+     */
+    private Exception exception;
+    
+    public AsyncUploadResult(DataIdentifier identifier, File file) {
+        super();
+        this.identifier = identifier;
+        this.file = file;
+    }
+
+    public DataIdentifier getIdentifier() {
+        return identifier;
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public Exception getException() {
+        return exception;
+    }
+
+    public void setException(Exception exception) {
+        this.exception = exception;
+    }
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/Backend.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/Backend.java
new file mode 100644
index 0000000..69611b0
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/Backend.java
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.core.data;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * The interface defines the backend which can be plugged into
+ * {@link CachingDataStore}.
+ */
+public interface Backend {
+
+    /**
+     * This method initialize backend with the configuration.
+     * 
+     * @param store
+     *            {@link CachingDataStore}
+     * @param homeDir
+     *            path of repository home dir.
+     * @param config
+     *            path of config property file.
+     * @throws DataStoreException
+     */
+    void init(CachingDataStore store, String homeDir, String config)
+            throws DataStoreException;
+
+    /**
+     * Return inputstream of record identified by identifier.
+     * 
+     * @param identifier
+     *            identifier of record.
+     * @return inputstream of the record.
+     * @throws DataStoreException
+     *             if record not found or any error.
+     */
+    InputStream read(DataIdentifier identifier) throws DataStoreException;
+
+    /**
+     * Return length of record identified by identifier.
+     * 
+     * @param identifier
+     *            identifier of record.
+     * @return length of the record.
+     * @throws DataStoreException
+     *             if record not found or any error.
+     */
+    long getLength(DataIdentifier identifier) throws DataStoreException;
+
+    /**
+     * Return lastModified of record identified by identifier.
+     * 
+     * @param identifier
+     *            identifier of record.
+     * @return lastModified of the record.
+     * @throws DataStoreException
+     *             if record not found or any error.
+     */
+    long getLastModified(DataIdentifier identifier) throws DataStoreException;
+
+    /**
+     * Stores file to backend with identifier used as key. If key pre-exists, it
+     * updates the timestamp of the key.
+     * 
+     * @param identifier
+     *            key of the file
+     * @param file
+     *            file that would be stored in backend.
+     * @throws DataStoreException
+     *             for any error.
+     */
+    void write(DataIdentifier identifier, File file) throws DataStoreException;
+
+    /**
+     * Write file to backend in asynchronous mode.
+     * 
+     * @param identifier
+     * @param file
+     * @param callback
+     *            Callback interface to called after upload succeed or failed.
+     * @throws DataStoreException
+     */
+    void writeAsync(DataIdentifier identifier, File file,
+            AsyncUploadCallback callback) throws DataStoreException;
+
+    /**
+     * Returns identifiers of all records that exists in backend.
+     * 
+     * @return iterator consisting of all identifiers
+     * @throws DataStoreException
+     */
+    Iterator<DataIdentifier> getAllIdentifiers() throws DataStoreException;
+
+    /**
+     * This method check the existence of record in backend. Return true if
+     * records exists else false. This method also touch record identified by
+     * identifier if touch is true.
+     * 
+     * @param identifier
+     * @throws DataStoreException
+     */
+    boolean exists(DataIdentifier identifier, boolean touch)
+            throws DataStoreException;
+
+    /**
+     * This method check the existence of record in backend.
+     * 
+     * @param identifier
+     *            identifier to be checked.
+     * @return true if records exists else false.
+     * @throws DataStoreException
+     */
+    boolean exists(DataIdentifier identifier) throws DataStoreException;
+   
+    /**
+     * Update the lastModified of record if it's lastModified < minModifiedDate.
+     * 
+     * @param identifier
+     * @param minModifiedDate
+     * @throws DataStoreException
+     */
+    void touch(final DataIdentifier identifier, long minModifiedDate)
+            throws DataStoreException;
+    
+    /**
+     * Update the lastModified of record if it's lastModified < minModifiedDate
+     * asynchronously. Result of update is passed using appropriate
+     * {@link AsyncTouchCallback} methods. If identifier's lastModified >
+     * minModified {@link AsyncTouchCallback#onAbort(AsyncTouchResult)} is
+     * called. Any exception is communicated through
+     * {@link AsyncTouchCallback#onFailure(AsyncTouchResult)} . On successful
+     * update of lastModified,
+     * {@link AsyncTouchCallback#onSuccess(AsyncTouchResult)(AsyncTouchResult)}
+     * is invoked.
+     * 
+     * @param identifier
+     * @param minModifiedDate
+     * @param callback
+     * @throws DataStoreException
+     */
+    void touchAsync(final DataIdentifier identifier, long minModifiedDate,
+            final AsyncTouchCallback callback) throws DataStoreException;
+
+    /**
+     * Close backend and release resources like database connection if any.
+     * 
+     * @throws DataStoreException
+     */
+    void close() throws DataStoreException;
+
+    /**
+     * Delete all records which are older than timestamp.
+     * 
+     * @param timestamp
+     * @return {@link Set} of identifiers which are deleted.
+     * @throws DataStoreException
+     */
+    Set<DataIdentifier> deleteAllOlderThan(long timestamp)
+            throws DataStoreException;
+
+    /**
+     * Delete record identified by identifier. No-op if identifier not found.
+     * 
+     * @param identifier
+     * @throws DataStoreException
+     */
+    void deleteRecord(DataIdentifier identifier) throws DataStoreException;
+}
+
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/CachingDataRecord.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/CachingDataRecord.java
new file mode 100644
index 0000000..8819117
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/CachingDataRecord.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.core.data;
+
+import java.io.InputStream;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * CachingDataRecord which stores reference to {@link CachingDataStore}. This
+ * class doesn't store any references to attributes but attributes are fetched
+ * on demand from {@link CachingDataStore}.
+ */
+public class CachingDataRecord extends AbstractDataRecord {
+
+    private static final Logger LOG = LoggerFactory.getLogger(CachingDataRecord.class);
+
+    private final CachingDataStore store;
+
+    public CachingDataRecord(CachingDataStore store, DataIdentifier identifier) {
+        super(store, identifier);
+        this.store = store;
+    }
+
+    @Override
+    public long getLastModified() {
+        try {
+            return store.getLastModified(getIdentifier());
+        } catch (DataStoreException dse) {
+            LOG.info("exception in getLastModified for identifier ["
+                + getIdentifier() + "]. returning 0.", dse);
+            return 0;
+        }
+    }
+
+    @Override
+    public long getLength() throws DataStoreException {
+        return store.getLength(getIdentifier());
+    }
+
+    @Override
+    public InputStream getStream() throws DataStoreException {
+        return store.getStream(getIdentifier());
+    }
+
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/CachingDataStore.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/CachingDataStore.java
new file mode 100644
index 0000000..2b015c2
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/CachingDataStore.java
@@ -0,0 +1,1396 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.core.data;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.lang.ref.WeakReference;
+import java.security.DigestOutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.core.data.util.NamedThreadFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A caching data store that consists of {@link LocalCache} and {@link Backend}.
+ * {@link Backend} is single source of truth. All methods first try to fetch
+ * information from {@link LocalCache}. If record is not available in
+ * {@link LocalCache}, then it is fetched from {@link Backend} and saved to
+ * {@link LocalCache} for further access. This class is designed to work without
+ * {@link LocalCache} and then all information is fetched from {@link Backend}.
+ * To disable {@link LocalCache} set {@link #setCacheSize(long)} to 0. *
+ * Configuration:
+ * 
+ * <pre>
+ * <DataStore class="org.apache.jackrabbit.aws.ext.ds.CachingDataStore">
+ * 
+ *     <param name="{@link #setPath(String) path}" value="/data/datastore"/>
+ *     <param name="{@link #setConfig(String) config}" value="${rep.home}/backend.properties"/>
+ *     <param name="{@link #setCacheSize(long) cacheSize}" value="68719476736"/>
+ *     <param name="{@link #setSecret(String) secret}" value="123456"/>
+ *     <param name="{@link #setCachePurgeTrigFactor(double)}" value="0.95d"/>
+ *     <param name="{@link #setCachePurgeResizeFactor(double) cacheSize}" value="0.85d"/>
+ *     <param name="{@link #setMinRecordLength(int) minRecordLength}" value="1024"/>
+ *     <param name="{@link #setContinueOnAsyncUploadFailure(boolean) continueOnAsyncUploadFailure}" value="false"/>
+ *     <param name="{@link #setConcurrentUploadsThreads(int) concurrentUploadsThreads}" value="10"/>
+ *     <param name="{@link #setAsyncUploadLimit(int) asyncUploadLimit}" value="100"/>
+ *     <param name="{@link #setUploadRetries(int) uploadRetries}" value="3"/>
+ *     <param name="{@link #setTouchAsync(boolean) touchAsync}" value="false"/>
+ *     <param name="{@link #setProactiveCaching(boolean) proactiveCaching}" value="true"/>
+ *     <param name="{@link #setRecLengthCacheSize(int) recLengthCacheSize}" value="200"/>
+ * &lt/DataStore>
+ */
+public abstract class CachingDataStore extends AbstractDataStore implements
+        MultiDataStoreAware, AsyncUploadCallback, AsyncTouchCallback {
+
+    /**
+     * Logger instance.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(CachingDataStore.class);
+
+    /**
+     * The digest algorithm used to uniquely identify records.
+     */
+    private static final String DIGEST = "SHA-1";
+
+    private static final String DS_STORE = ".DS_Store";
+
+    /**
+     * Name of the directory used for temporary files. Must be at least 3
+     * characters.
+     */
+    private static final String TMP = "tmp";
+
+    /**
+     * All data identifiers that are currently in use are in this set until they
+     * are garbage collected.
+     */
+    protected Map<DataIdentifier, WeakReference<DataIdentifier>> inUse = Collections.synchronizedMap(new WeakHashMap<DataIdentifier, WeakReference<DataIdentifier>>());
+    
+    /**
+     * In memory map to hold failed asynchronous upload {@link DataIdentifier}
+     * and its retry count. Once if all retries are exhausted or file is 
+     * successfully uploaded, then corresponding entry is flushed from the map.
+     * As all failed uploads are synchronously uploaded at startup, this map 
+     * is not required to be persisted. 
+     */
+    protected final Map<DataIdentifier, Integer> uploadRetryMap = new ConcurrentHashMap<DataIdentifier, Integer>(5);
+    
+    /**
+     * In memory map to hold in-progress asynchronous touch. Once touch is
+     * successful corresponding entry is flushed from the map.
+     */
+    protected final Map<DataIdentifier, Long> asyncTouchCache = new ConcurrentHashMap<DataIdentifier, Long>(5);
+
+    /**
+     * In memory map to hold in-progress asynchronous downloads. Once
+     * download is finished corresponding entry is flushed from the map.
+     */
+    protected final Map<DataIdentifier, Long> asyncDownloadCache = new ConcurrentHashMap<DataIdentifier, Long>(5);
+
+    /**
+     * In memory cache to hold {@link DataRecord#getLength()} against
+     * {@link DataIdentifier}
+     */
+    protected Map<DataIdentifier, Long> recLenCache = null;
+
+    protected Backend backend;
+
+    /**
+     * The minimum size of an object that should be stored in this data store.
+     */
+    private int minRecordLength = 16 * 1024;
+
+    private String path;
+
+    private File directory;
+
+    private File tmpDir;
+
+    private String secret;
+    
+    /**
+     * Flag to indicate if lastModified is updated asynchronously.
+     */
+    private boolean touchAsync = false;
+
+    /**
+     * Flag to indicate that binary content will be cached proactively and
+     * asynchronously when binary metadata is retrieved from {@link Backend}.
+     */
+    private boolean proactiveCaching = true;
+
+    /**
+     * The optional backend configuration.
+     */
+    private String config;
+
+    /**
+     * The minimum modified date. If a file is accessed (read or write) with a
+     * modified date older than this value, the modified date is updated to the
+     * current time.
+     */
+    private long minModifiedDate;
+
+    /**
+     * Cache purge trigger factor. Cache will undergo in auto-purge mode if
+     * cache current size is greater than cachePurgeTrigFactor * cacheSize
+     */
+    private double cachePurgeTrigFactor = 0.95d;
+
+    /**
+     * Cache resize factor. After auto-purge mode, cache current size would just
+     * greater than cachePurgeResizeFactor * cacheSize cacheSize
+     */
+    private double cachePurgeResizeFactor = 0.85d;
+
+    /**
+     * The number of bytes in the cache. The default value is 64 GB.
+     */
+    private long cacheSize = 64L * 1024 * 1024 * 1024;
+    
+    /**
+     * The number of retries for failed upload.
+     */
+    private int uploadRetries = 3;
+
+    /**
+     * The local file system cache.
+     */
+    private LocalCache cache;
+
+    /**
+     * Caching holding pending uploads
+     */
+    private AsyncUploadCache asyncWriteCache;
+
+    /**
+     * {@link ExecutorService} to asynchronous downloads
+     */
+    private ExecutorService downloadExecService;
+
+    protected abstract Backend createBackend();
+
+    protected abstract String getMarkerFile();
+
+    /**
+     * In {@link #init(String)},it resumes all incomplete asynchronous upload
+     * from {@link AsyncUploadCache} and uploads them concurrently in multiple
+     * threads. It throws {@link RepositoryException}, if file is not found in
+     * local cache for that asynchronous upload. As far as code is concerned, it
+     * is only possible when somebody has removed files from local cache
+     * manually. If there is an exception and user want to proceed with
+     * inconsistencies, set parameter continueOnAsyncUploadFailure to true in
+     * repository.xml. This will ignore {@link RepositoryException} and log all
+     * missing files and proceed after resetting {@link AsyncUploadCache} .
+     */
+    private boolean continueOnAsyncUploadFailure;
+
+    /**
+     * The {@link #init(String)} methods checks for {@link #getMarkerFile()} and
+     * if it doesn't exists migrates all files from fileystem to {@link Backend}
+     * . This parameter governs number of threads which will upload files
+     * concurrently to {@link Backend}.
+     */
+    private int concurrentUploadsThreads = 10;
+
+    /**
+     * This parameter limits the number of asynchronous uploads slots to
+     * {@link Backend}. Once this limit is reached, further uploads to
+     * {@link Backend} are synchronous, till one of asynchronous uploads
+     * completes and make asynchronous uploads slot available. To disable
+     * asynchronous upload, set {@link #asyncUploadLimit} parameter to 0 in
+     * repository.xml. By default it is 100
+     */
+    private int asyncUploadLimit = 100;
+    
+    /**
+     * Size of {@link #recLenCache}. Each entry consumes of approx 140 bytes.
+     * Default total memory consumption of {@link #recLenCache} 28KB.
+     */
+    private int recLengthCacheSize = 200;
+
+    /**
+     * Initialized the data store. If the path is not set, <repository
+     * home>/repository/datastore is used. This directory is automatically
+     * created if it does not yet exist. During first initialization, it upload
+     * all files from local datastore to backed and local datastore act as a
+     * local cache.
+     */
+    @Override
+    public void init(String homeDir) throws RepositoryException {
+        try {
+            if (path == null) {
+                path = homeDir + "/repository/datastore";
+            }
+            // create tmp inside path
+            tmpDir = new File(path, "tmp");
+            LOG.info("path=[{}],  tmpPath=[{}]", path, tmpDir.getAbsolutePath());
+            directory = new File(path);
+            mkdirs(directory);
+            if (!mkdirs(tmpDir)) {
+                FileUtils.cleanDirectory(tmpDir);
+                LOG.info("tmp=[{}] cleaned.", tmpDir.getPath());
+            }
+            boolean asyncWriteCacheInitStatus = true;
+            try {
+                asyncWriteCache = new AsyncUploadCache();
+                asyncWriteCache.init(homeDir, path, asyncUploadLimit);
+            } catch (Exception e) {
+                LOG.warn("Failed to initialize asyncWriteCache", e);
+                asyncWriteCacheInitStatus = false;
+            }
+            backend = createBackend();
+            backend.init(this, path, config);
+            String markerFileName = getMarkerFile();
+            if (markerFileName != null && !"".equals(markerFileName.trim())) {
+                // create marker file in homeDir to avoid deletion in cache
+                // cleanup.
+                File markerFile = new File(homeDir, markerFileName);
+                if (!markerFile.exists()) {
+                    LOG.info("load files from local cache");
+                    uploadFilesFromCache();
+                    try {
+                        markerFile.createNewFile();
+                    } catch (IOException e) {
+                        throw new DataStoreException(
+                            "Could not create marker file "
+                                + markerFile.getAbsolutePath(), e);
+                    }
+                } else {
+                    LOG.info("marker file = [{}] exists ",
+                        markerFile.getAbsolutePath());
+                    if (!asyncWriteCacheInitStatus) {
+                        LOG.info("Initialization of asyncWriteCache failed. "
+                            + "Re-loading all files from local cache");
+                        uploadFilesFromCache();
+                        asyncWriteCache.reset();
+                    }
+                }
+            } else {
+                throw new DataStoreException("Failed to intialized DataStore."
+                    + " MarkerFileName is null or empty. ");
+            }
+            // upload any leftover async uploads to backend during last shutdown
+            Set<String> fileList = asyncWriteCache.getAll();
+            if (fileList != null && !fileList.isEmpty()) {
+                List<String> errorFiles = new ArrayList<String>();
+                LOG.info("Uploading [{}] and size=[{}] from AsyncUploadCache.",
+                    fileList, fileList.size());
+                long totalSize = 0;
+                List<File> files = new ArrayList<File>(fileList.size());
+                for (String fileName : fileList) {
+                    File f = new File(path, fileName);
+                    if (!f.exists()) {
+                        errorFiles.add(fileName);
+                        LOG.error(
+                            "Cannot upload pending file [{}]. File doesn't exist.",
+                            f.getAbsolutePath());
+                    } else {
+                        totalSize += f.length();
+                        files.add(new File(path, fileName));
+                    }
+                }
+                new FilesUploader(files, totalSize, concurrentUploadsThreads,
+                    true).upload();
+                if (!continueOnAsyncUploadFailure && errorFiles.size() > 0) {
+                    LOG.error(
+                        "Pending uploads of files [{}] failed. Files do not exist in Local cache.",
+                        errorFiles);
+                    LOG.error("To continue set [continueOnAsyncUploadFailure] "
+                        + "to true in Datastore configuration in "
+                        + "repository.xml. There would be inconsistent data "
+                        + "in repository due the missing files. ");
+                    throw new RepositoryException(
+                        "Cannot upload async uploads from local cache. Files not found.");
+                } else {
+                    if (errorFiles.size() > 0) {
+                        LOG.error(
+                            "Pending uploads of files [{}] failed. Files do" +
+                            " not exist in Local cache. Continuing as " +
+                            "[continueOnAsyncUploadFailure] is set to true.",
+                            errorFiles);
+                    }
+                    LOG.info("Reseting AsyncWrite Cache list.");
+                    asyncWriteCache.reset();
+                }
+            }
+            downloadExecService = Executors.newFixedThreadPool(5,
+                new NamedThreadFactory("backend-file-download-worker"));
+            cache = new LocalCache(path, tmpDir.getAbsolutePath(), cacheSize,
+                cachePurgeTrigFactor, cachePurgeResizeFactor, asyncWriteCache);
+            /*
+             * Initialize LRU cache of size {@link #recLengthCacheSize}
+             */
+            recLenCache = Collections.synchronizedMap(new LinkedHashMap<DataIdentifier, Long>(
+                recLengthCacheSize, 0.75f, true) {
+
+                private static final long serialVersionUID = -8752749075395630485L;
+
+                @Override
+                protected boolean removeEldestEntry(
+                                Map.Entry<DataIdentifier, Long> eldest) {
+                    if (size() > recLengthCacheSize) {
+                        LOG.trace("evicted from recLengthCache [{}]",
+                            eldest.getKey());
+                        return true;
+                    }
+                    return false;
+                }
+            });
+        } catch (Exception e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    /**
+     * Creates a new data record in {@link Backend}. The stream is first
+     * consumed and the contents are saved in a temporary file and the SHA-1
+     * message digest of the stream is calculated. If a record with the same
+     * SHA-1 digest (and length) is found then it is returned. Otherwise new
+     * record is created in {@link Backend} and the temporary file is moved in
+     * place to {@link LocalCache}.
+     * 
+     * @param input
+     *            binary stream
+     * @return {@link CachingDataRecord}
+     * @throws DataStoreException
+     *             if the record could not be created.
+     */
+    @Override
+    public DataRecord addRecord(InputStream input) throws DataStoreException {
+        File temporary = null;
+        long startTime = System.currentTimeMillis();
+        long length = 0;
+        try {
+            temporary = newTemporaryFile();
+            DataIdentifier tempId = new DataIdentifier(temporary.getName());
+            usesIdentifier(tempId);
+            // Copy the stream to the temporary file and calculate the
+            // stream length and the message digest of the stream
+            MessageDigest digest = MessageDigest.getInstance(DIGEST);
+            OutputStream output = new DigestOutputStream(new FileOutputStream(
+                temporary), digest);
+            try {
+                length = IOUtils.copyLarge(input, output);
+            } finally {
+                output.close();
+            }
+            long currTime = System.currentTimeMillis();
+            DataIdentifier identifier = new DataIdentifier(
+                encodeHexString(digest.digest()));
+            LOG.debug("SHA1 of [{}], length =[{}] took [{}]ms ",
+                new Object[] { identifier, length, (currTime - startTime) });
+            String fileName = getFileName(identifier);
+            AsyncUploadCacheResult result = null;
+            synchronized (this) {
+                usesIdentifier(identifier);
+                // check if async upload is already in progress
+                if (!asyncWriteCache.hasEntry(fileName, true)) {
+                    result = cache.store(fileName, temporary, true);
+                }
+            }
+            LOG.debug("storing  [{}] in localCache took [{}] ms", identifier,
+                (System.currentTimeMillis() - currTime));
+            if (result != null) {
+                if (result.canAsyncUpload()) {
+                    backend.writeAsync(identifier, result.getFile(), this);
+                } else {
+                    backend.write(identifier, result.getFile());
+                }
+            }
+            // this will also make sure that
+            // tempId is not garbage collected until here
+            inUse.remove(tempId);
+            LOG.debug("addRecord [{}] of length [{}] took [{}]ms.",
+                new Object[] { identifier, length,
+                    (System.currentTimeMillis() - startTime) });
+            return new CachingDataRecord(this, identifier);
+        } catch (NoSuchAlgorithmException e) {
+            throw new DataStoreException(DIGEST + " not available", e);
+        } catch (IOException e) {
+            throw new DataStoreException("Could not add record", e);
+        } finally {
+            if (temporary != null) {
+                // try to delete - but it's not a big deal if we can't
+                temporary.delete();
+            }
+        }
+    }
+
+    @Override
+    public DataRecord getRecord(DataIdentifier identifier)
+                    throws DataStoreException {
+        String fileName = getFileName(identifier);
+        try {
+            if (getLength(identifier) > -1) {
+                LOG.trace("getRecord: [{}]  retrieved using getLength",
+                    identifier);
+                touchInternal(identifier);
+                usesIdentifier(identifier);
+                return new CachingDataRecord(this, identifier);
+            } else if (asyncWriteCache.hasEntry(fileName, minModifiedDate > 0)) {
+                LOG.trace("getRecord: [{}]  retrieved from asyncUploadmap",
+                    identifier);
+                usesIdentifier(identifier);
+                return new CachingDataRecord(this, identifier);
+            }
+        } catch (IOException ioe) {
+            throw new DataStoreException("error in getting record ["
+                + identifier + "]", ioe);
+        }
+        throw new DataStoreException("Record not found: " + identifier);
+    }
+    
+    /**
+     * Get a data record for the given identifier or null it data record doesn't
+     * exist in {@link Backend}
+     * 
+     * @param identifier identifier of record.
+     * @return the {@link CachingDataRecord} or null.
+     */
+    @Override
+    public DataRecord getRecordIfStored(DataIdentifier identifier)
+                    throws DataStoreException {
+        String fileName = getFileName(identifier);
+        try {
+            if (asyncWriteCache.hasEntry(fileName, minModifiedDate > 0)) {
+                LOG.trace(
+                    "getRecordIfStored: [{}]  retrieved from asyncuploadmap",
+                    identifier);
+                usesIdentifier(identifier);
+                return new CachingDataRecord(this, identifier);
+            } else if (recLenCache.containsKey(identifier)) {
+                LOG.trace(
+                    "getRecordIfStored: [{}]  retrieved using recLenCache",
+                    identifier);
+                touchInternal(identifier);
+                usesIdentifier(identifier);
+                return new CachingDataRecord(this, identifier);
+            } else {
+                try {
+                    long length = backend.getLength(identifier);
+                    LOG.debug(
+                        "getRecordIfStored :[{}]  retrieved from backend",
+                        identifier);
+                    recLenCache.put(identifier, length);
+                    touchInternal(identifier);
+                    usesIdentifier(identifier);
+                    return new CachingDataRecord(this, identifier);
+                } catch (DataStoreException ignore) {
+                    LOG.warn(" getRecordIfStored: [{}]  not found", identifier);
+                }
+
+            }
+        } catch (IOException ioe) {
+            throw new DataStoreException(ioe);
+        }
+        return null;
+    }
+
+    @Override
+    public void updateModifiedDateOnAccess(long before) {
+        LOG.info("minModifiedDate set to [{}]", before);
+        minModifiedDate = before;
+    }
+
+    /**
+     * Retrieves all identifiers from {@link Backend}.
+     */
+    @Override
+    public Iterator<DataIdentifier> getAllIdentifiers()
+            throws DataStoreException {
+        Set<DataIdentifier> ids = new HashSet<DataIdentifier>();
+        for (String fileName : asyncWriteCache.getAll()) {
+            ids.add(getIdentifier(fileName));
+        }
+        Iterator<DataIdentifier> itr = backend.getAllIdentifiers();
+        while (itr.hasNext()) {
+            ids.add(itr.next());
+        }
+        return ids.iterator();
+    }
+
+    /**
+     * This method deletes record from {@link Backend} and then from
+     * {@link LocalCache}
+     */
+    @Override
+    public void deleteRecord(DataIdentifier identifier)
+            throws DataStoreException {
+        String fileName = getFileName(identifier);
+        synchronized (this) {
+            try {
+                // order is important here
+                recLenCache.remove(identifier);
+                asyncWriteCache.delete(fileName);
+                backend.deleteRecord(identifier);
+                cache.delete(fileName);
+            } catch (IOException ioe) {
+                throw new DataStoreException(ioe);
+            }
+        }
+    }
+
+    @Override
+    public synchronized int deleteAllOlderThan(long min)
+            throws DataStoreException {
+        Set<DataIdentifier> diSet = backend.deleteAllOlderThan(min);
+        
+        // remove entries from local cache
+        for (DataIdentifier identifier : diSet) {
+            recLenCache.remove(identifier);
+            cache.delete(getFileName(identifier));
+        }
+        try {
+            for (String fileName : asyncWriteCache.deleteOlderThan(min)) {
+                diSet.add(getIdentifier(fileName));
+            }
+        } catch (IOException e) {
+            throw new DataStoreException(e);
+        }
+        LOG.info(
+            "deleteAllOlderThan  exit. Deleted [{}]records. Number of records deleted [{}]",
+            diSet, diSet.size());
+        return diSet.size();
+    }
+
+    /**
+     * Get stream of record from {@link LocalCache}. If record is not available
+     * in {@link LocalCache}, this method fetches record from {@link Backend}
+     * and stores it to {@link LocalCache}. Stream is then returned from cached
+     * record.
+     */
+    InputStream getStream(DataIdentifier identifier) throws DataStoreException {
+        InputStream in = null;
+        try {
+            String fileName = getFileName(identifier);
+            InputStream cached = cache.getIfStored(fileName);
+            if (cached != null) {
+                return cached;
+            }
+            in = backend.read(identifier);
+            return cache.store(fileName, in);
+        } catch (IOException e) {
+            throw new DataStoreException("IO Exception: " + identifier, e);
+        } finally {
+            IOUtils.closeQuietly(in);
+        }
+    }
+
+    /**
+     * Return lastModified of record from {@link Backend} assuming
+     * {@link Backend} as a single source of truth.
+     */
+    public long getLastModified(DataIdentifier identifier)
+            throws DataStoreException {
+        String fileName = getFileName(identifier);
+        long lastModified = asyncWriteCache.getLastModified(fileName);
+        if (lastModified != 0) {
+            LOG.trace(
+                "identifier [{}], lastModified=[{}] retrireved from AsyncUploadCache ",
+                identifier, lastModified);
+
+        } else if (asyncTouchCache.get(identifier) != null) {
+            lastModified = asyncTouchCache.get(identifier);
+            LOG.trace(
+                "identifier [{}], lastModified=[{}] retrireved from asyncTouchCache ",
+                identifier, lastModified);
+        } else {
+            lastModified = backend.getLastModified(identifier);
+            LOG.debug(
+                "identifier [{}], lastModified=[{}] retrireved from backend ",
+                identifier, lastModified);
+            asyncDownload(identifier);
+        }
+        return lastModified;
+    }
+
+    /**
+     * Return the length of record from {@link LocalCache} if available,
+     * otherwise retrieve it from {@link Backend}.
+     */
+    public long getLength(final DataIdentifier identifier)
+                    throws DataStoreException {
+        String fileName = getFileName(identifier);
+
+        Long length = recLenCache.get(identifier);
+        if (length != null) {
+            LOG.trace(" identifier [{}] length fetched from recLengthCache",
+                identifier);
+            return length;
+        } else if ((length = cache.getFileLength(fileName)) != null) {
+            LOG.trace(" identifier [{}] length fetched from local cache",
+                identifier);
+            recLenCache.put(identifier, length);
+            return length;
+        } else {
+            length = backend.getLength(identifier);
+            LOG.debug(" identifier [{}] length fetched from backend",
+                identifier);
+            recLenCache.put(identifier, length);
+            asyncDownload(identifier);
+            return length;
+        }
+    }
+
+    @Override
+    protected byte[] getOrCreateReferenceKey() throws DataStoreException {
+        try {
+            return secret.getBytes("UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new DataStoreException(e);
+        }
+    }
+
+    public Set<String> getPendingUploads() {
+        return asyncWriteCache.getAll();
+    }
+    
+    
+    public void deleteFromCache(DataIdentifier identifier)
+                    throws DataStoreException {
+        try {
+            // order is important here
+            recLenCache.remove(identifier);
+            String fileName = getFileName(identifier);
+            asyncWriteCache.delete(fileName);
+            cache.delete(fileName);
+        } catch (IOException ioe) {
+            throw new DataStoreException(ioe);
+        }
+    }
+    
+    @Override
+    public void onSuccess(AsyncUploadResult result) {
+        DataIdentifier identifier = result.getIdentifier();
+        File file = result.getFile();
+        String fileName = getFileName(identifier);
+        try {
+            LOG.debug("Upload completed for [{}]", identifier);
+            // remove from failed upload map if any.
+            uploadRetryMap.remove(identifier);
+            AsyncUploadCacheResult cachedResult = asyncWriteCache.remove(fileName);
+            if (cachedResult.doRequiresDelete()) {
+                // added record already marked for delete
+                deleteRecord(identifier);
+            } else {
+                // async upload took lot of time.
+                // getRecord to touch if required.
+                getRecord(identifier);
+            }
+        } catch (IOException ie) {
+            LOG.warn("Cannot remove pending file upload. Dataidentifer [ "
+                + identifier + "], file [" + file.getAbsolutePath() + "]", ie);
+        } catch (DataStoreException dse) {
+            LOG.warn("Cannot remove pending file upload. Dataidentifer [ "
+                + identifier + "], file [" + file.getAbsolutePath() + "]", dse);
+        }
+    }
+
+    @Override
+    public void onFailure(AsyncUploadResult result) {
+        DataIdentifier identifier = result.getIdentifier();
+        File file = result.getFile();
+        String fileName = getFileName(identifier);
+        if (result.getException() != null) {
+            LOG.warn("Async Upload failed. Dataidentifer [ " + identifier
+                + "], file [" + file.getAbsolutePath() + "]",
+                result.getException());
+        } else {
+            LOG.warn("Async Upload failed. Dataidentifer [ " + identifier
+                + "], file [" + file.getAbsolutePath() + "]");
+        }
+        // Retry failed upload upto uploadRetries times.
+        try {
+            if (asyncWriteCache.hasEntry(fileName, false)) {
+                synchronized (uploadRetryMap) {
+                    Integer retry = uploadRetryMap.get(identifier);
+                    if (retry == null) {
+                        retry = new Integer(1);
+                    } else {
+                        retry++;
+                    }
+                    if (retry <= uploadRetries) {
+                        uploadRetryMap.put(identifier, retry);
+                        LOG.info(
+                            "Retrying [{}] times failed upload for dataidentifer",
+                            retry, identifier);
+                        try {
+                            backend.writeAsync(identifier, file, this);
+                        } catch (DataStoreException e) {
+                            LOG.warn("exception", e);
+                        }
+                    } else {
+                        LOG.info("Retries [{}] exhausted for  dataidentifer.",
+                            (retry - 1), identifier);
+                        uploadRetryMap.remove(identifier);
+                    }
+                }
+            }
+        } catch (IOException ie) {
+            LOG.warn("Cannot retry failed async file upload. Dataidentifer [ "
+                + identifier + "], file [" + file.getAbsolutePath() + "]", ie);
+        }
+    }
+
+    @Override
+    public void onAbort(AsyncUploadResult result) {
+        DataIdentifier identifier = result.getIdentifier();
+        File file = result.getFile();
+        String fileName = getFileName(identifier);
+        try {
+            // remove from failed upload map if any.
+            uploadRetryMap.remove(identifier);
+            asyncWriteCache.remove(fileName);
+            LOG.info(
+                "Async Upload Aborted. Dataidentifer [{}], file [{}] removed from AsyncCache.",
+                identifier, file.getAbsolutePath());
+        } catch (IOException ie) {
+            LOG.warn("Cannot remove pending file upload. Dataidentifer [ "
+                + identifier + "], file [" + file.getAbsolutePath() + "]", ie);
+        }
+    }
+
+    
+    @Override
+    public void onSuccess(AsyncTouchResult result) {
+        asyncTouchCache.remove(result.getIdentifier());
+        LOG.debug(" Async Touch succeed. Removed [{}] from asyncTouchCache",
+            result.getIdentifier());
+
+    }
+    
+    @Override
+    public void onFailure(AsyncTouchResult result) {
+        LOG.warn(" Async Touch failed. Not removing [{}] from asyncTouchCache",
+            result.getIdentifier());
+        if (result.getException() != null) {
+            LOG.debug(" Async Touch failed. exception", result.getException());
+        }
+    }
+    
+    @Override
+    public void onAbort(AsyncTouchResult result) {
+        asyncTouchCache.remove(result.getIdentifier());
+        LOG.debug(" Async Touch aborted. Removed [{}] from asyncTouchCache",
+            result.getIdentifier());
+    }
+    
+    /**
+     * Method to confirm that identifier can be deleted from {@link Backend}
+     * 
+     * @param identifier
+     * @return
+     */
+    public boolean confirmDelete(DataIdentifier identifier) {
+        if (isInUse(identifier)) {
+            LOG.debug("identifier [{}] is inUse confirmDelete= false ",
+                identifier);
+            return false;
+        }
+
+        String fileName = getFileName(identifier);
+        long lastModified = asyncWriteCache.getLastModified(fileName);
+        if (lastModified != 0) {
+            LOG.debug(
+                "identifier [{}] is asyncWriteCache map confirmDelete= false ",
+                identifier);
+            return false;
+
+        }
+        if (asyncTouchCache.get(identifier) != null) {
+            LOG.debug(
+                "identifier [{}] is asyncTouchCache confirmDelete = false ",
+                identifier);
+            return false;
+        }
+
+        return true;
+    }
+    
+    /**
+     * Internal method to touch identifier in @link {@link Backend}. if
+     * {@link #touchAsync}, the record is updated asynchronously.
+     * 
+     * @param identifier
+     * @throws DataStoreException
+     */
+    private void touchInternal(DataIdentifier identifier)
+            throws DataStoreException {
+
+        if (touchAsync) {
+            Long lastModified = asyncTouchCache.put(identifier,
+                System.currentTimeMillis());
+
+            if (lastModified == null) {
+                LOG.debug("Async touching [{}] ", identifier);
+                backend.touchAsync(identifier, minModifiedDate, this);
+            } else {
+                LOG.debug( "Touched in asyncTouchMap [{}]", identifier);
+            }
+                
+        } else {
+            backend.touch(identifier, minModifiedDate);
+        }
+    }
+    
+    /**
+     * Invoke {@link #getStream(DataIdentifier)} asynchronously to cache binary
+     * asynchronously.
+     */
+    private void asyncDownload(final DataIdentifier identifier) {
+        if (proactiveCaching
+            && cacheSize != 0
+            && asyncDownloadCache.put(identifier, System.currentTimeMillis()) == null) {
+            downloadExecService.execute(new Runnable() {
+                @Override
+                public void run() {
+                    long startTime = System.currentTimeMillis();
+                    InputStream input = null;
+                    try {
+                        LOG.trace("Async download [{}] started.", identifier);
+                        input = getStream(identifier);
+                    } catch (RepositoryException re) {
+                        // ignore exception
+                    } finally {
+                        asyncDownloadCache.remove(identifier);
+                        IOUtils.closeQuietly(input);
+                        LOG.debug("Async download [{}] completed in [{}] ms.",
+                            identifier,
+                            (System.currentTimeMillis() - startTime));
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * Returns a unique temporary file to be used for creating a new data
+     * record.
+     */
+    private File newTemporaryFile() throws IOException {
+        return File.createTempFile(TMP, null, tmpDir);
+    }
+
+    /**
+     * Load files from {@link LocalCache} to {@link Backend}.
+     */
+    private void uploadFilesFromCache() throws RepositoryException {
+        ArrayList<File> files = new ArrayList<File>();
+        listRecursive(files, directory);
+        long totalSize = 0;
+        for (File f : files) {
+            totalSize += f.length();
+        }
+        if (files.size() > 0) {
+            if (concurrentUploadsThreads > 1) {
+                new FilesUploader(files, totalSize, concurrentUploadsThreads,
+                    false).upload();
+            } else {
+                uploadFilesInSingleThread(files, totalSize);
+            }
+        }
+    }
+
+    private void uploadFilesInSingleThread(List<File> files, long totalSize)
+            throws RepositoryException {
+        long startTime = System.currentTimeMillis();
+        LOG.info("Upload:  [{}] files in single thread.", files.size());
+        long currentCount = 0;
+        long currentSize = 0;
+        long time = System.currentTimeMillis();
+        for (File f : files) {
+            String name = f.getName();
+            LOG.debug("upload file [{}] ", name);
+            if (!name.startsWith(TMP) && !name.endsWith(DS_STORE)
+                && f.length() > 0) {
+                uploadFileToBackEnd(f, false);
+            }
+            currentSize += f.length();
+            currentCount++;
+            long now = System.currentTimeMillis();
+            if (now > time + 5000) {
+                LOG.info("Uploaded:  [{}/{}] files, [{}/{}] size data",
+                    new Object[] { currentCount, files.size(), currentSize,
+                        totalSize });
+                time = now;
+            }
+        }
+        long endTime = System.currentTimeMillis();
+        LOG.info(
+            "Uploaded:  [{}/{}] files, [{}/{}] size data, time taken = [{}] sec",
+            new Object[] { currentCount, files.size(), currentSize, totalSize,
+                ((endTime - startTime) / 1000) });
+    }
+
+    /**
+     * Traverse recursively and populate list with files.
+     */
+    private static void listRecursive(List<File> list, File file) {
+        File[] files = file.listFiles();
+        if (files != null) {
+            for (File f : files) {
+                if (f.isDirectory()) {
+                    listRecursive(list, f);
+                } else {
+                    list.add(f);
+                }
+            }
+        }
+    }
+
+    /**
+     * Upload file from {@link LocalCache} to {@link Backend}.
+     * 
+     * @param f
+     *            file to uploaded.
+     * @throws DataStoreException
+     */
+    private void uploadFileToBackEnd(File f, boolean updateAsyncUploadCache)
+            throws DataStoreException {
+        try {
+            DataIdentifier identifier = new DataIdentifier(f.getName());
+            usesIdentifier(identifier);
+            backend.write(identifier, f);
+            if (updateAsyncUploadCache) {
+                String fileName = getFileName(identifier);
+                asyncWriteCache.remove(fileName);
+            }
+            LOG.debug("uploaded [{}]", f.getName());
+        } catch (IOException ioe) {
+            throw new DataStoreException(ioe);
+        }
+    }
+
+    /**
+     * Derive file name from identifier.
+     */
+    private static String getFileName(DataIdentifier identifier) {
+        String name = identifier.toString();
+        return getFileName(name);
+    }
+
+    private static String getFileName(String name) {
+        return name.substring(0, 2) + "/" + name.substring(2, 4) + "/"
+            + name.substring(4, 6) + "/" + name;
+    }
+
+    private static DataIdentifier getIdentifier(String fileName) {
+        return new DataIdentifier(
+            fileName.substring(fileName.lastIndexOf("/") + 1));
+    }
+
+    private void usesIdentifier(DataIdentifier identifier) {
+        inUse.put(identifier, new WeakReference<DataIdentifier>(identifier));
+    }
+
+    private static boolean mkdirs(File dir) throws IOException {
+        if (dir.exists()) {
+            if (dir.isFile()) {
+                throw new IOException("Can not create a directory "
+                    + "because a file exists with the same name: "
+                    + dir.getAbsolutePath());
+            }
+            return false;
+        }
+        boolean created = dir.mkdirs();
+        if (!created) {
+            throw new IOException("Could not create directory: "
+                + dir.getAbsolutePath());
+        }
+        return created;
+    }
+
+    @Override
+    public void clearInUse() {
+        inUse.clear();
+    }
+
+    public boolean isInUse(DataIdentifier identifier) {
+        return inUse.containsKey(identifier);
+    }
+
+    @Override
+    public void close() throws DataStoreException {
+        cache.close();
+        backend.close();
+        downloadExecService.shutdown();
+    }
+
+    /**
+     * Setter for configuration based secret
+     * 
+     * @param secret
+     *            the secret used to sign reference binaries
+     */
+    public void setSecret(String secret) {
+        this.secret = secret;
+    }
+
+    /**
+     * Set the minimum object length.
+     * 
+     * @param minRecordLength
+     *            the length
+     */
+    public void setMinRecordLength(int minRecordLength) {
+        this.minRecordLength = minRecordLength;
+    }
+
+    /**
+     * Return mininum object length.
+     */
+    @Override
+    public int getMinRecordLength() {
+        return minRecordLength;
+    }
+
+    /**
+     * Return path of configuration properties.
+     * 
+     * @return path of configuration properties.
+     */
+    public String getConfig() {
+        return config;
+    }
+
+    /**
+     * Set the configuration properties path.
+     * 
+     * @param config
+     *            path of configuration properties.
+     */
+    public void setConfig(String config) {
+        this.config = config;
+    }
+
+    /**
+     * @return size of {@link LocalCache}.
+     */
+    public long getCacheSize() {
+        return cacheSize;
+    }
+
+    /**
+     * Set size of {@link LocalCache}.
+     * 
+     * @param cacheSize
+     *            size of {@link LocalCache}.
+     */
+    public void setCacheSize(long cacheSize) {
+        this.cacheSize = cacheSize;
+    }
+
+    /**
+     * @return path of {@link LocalCache}.
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     * Set path of {@link LocalCache}.
+     * 
+     * @param path
+     *            of {@link LocalCache}.
+     */
+    public void setPath(String path) {
+        this.path = path;
+    }
+
+    /**
+     * @return Purge trigger factor of {@link LocalCache}.
+     */
+    public double getCachePurgeTrigFactor() {
+        return cachePurgeTrigFactor;
+    }
+
+    /**
+     * Set purge trigger factor of {@link LocalCache}.
+     * 
+     * @param cachePurgeTrigFactor
+     *            purge trigger factor.
+     */
+    public void setCachePurgeTrigFactor(double cachePurgeTrigFactor) {
+        this.cachePurgeTrigFactor = cachePurgeTrigFactor;
+    }
+
+    /**
+     * @return Purge resize factor of {@link LocalCache}.
+     */
+    public double getCachePurgeResizeFactor() {
+        return cachePurgeResizeFactor;
+    }
+
+    /**
+     * Set purge resize factor of {@link LocalCache}.
+     * 
+     * @param cachePurgeResizeFactor
+     *            purge resize factor.
+     */
+    public void setCachePurgeResizeFactor(double cachePurgeResizeFactor) {
+        this.cachePurgeResizeFactor = cachePurgeResizeFactor;
+    }
+
+    public int getConcurrentUploadsThreads() {
+        return concurrentUploadsThreads;
+    }
+
+    public void setConcurrentUploadsThreads(int concurrentUploadsThreads) {
+        this.concurrentUploadsThreads = concurrentUploadsThreads;
+    }
+
+    public int getAsyncUploadLimit() {
+        return asyncUploadLimit;
+    }
+
+    public void setAsyncUploadLimit(int asyncUploadLimit) {
+        this.asyncUploadLimit = asyncUploadLimit;
+    }
+
+    public boolean isContinueOnAsyncUploadFailure() {
+        return continueOnAsyncUploadFailure;
+    }
+
+    public void setContinueOnAsyncUploadFailure(
+            boolean continueOnAsyncUploadFailure) {
+        this.continueOnAsyncUploadFailure = continueOnAsyncUploadFailure;
+    }
+    
+    public int getUploadRetries() {
+        return uploadRetries;
+    }
+
+    public void setUploadRetries(int uploadRetries) {
+        this.uploadRetries = uploadRetries;
+    }
+
+    public void setTouchAsync(boolean touchAsync) {
+        this.touchAsync = touchAsync;
+    }
+
+    public void setProactiveCaching(boolean proactiveCaching) {
+        this.proactiveCaching = proactiveCaching;
+    }
+    
+    public void setRecLengthCacheSize(int recLengthCacheSize) {
+        this.recLengthCacheSize = recLengthCacheSize;
+    }
+
+    public Backend getBackend() {
+        return backend;
+    }
+
+    /**
+     * This class initiates files upload in multiple threads to backend.
+     */
+    private class FilesUploader {
+        final List<File> files;
+
+        final long totalSize;
+
+        volatile AtomicInteger currentCount = new AtomicInteger();
+
+        volatile AtomicLong currentSize = new AtomicLong();
+
+        volatile AtomicBoolean exceptionRaised = new AtomicBoolean();
+
+        DataStoreException exception;
+
+        final int threads;
+
+        final boolean updateAsyncCache;
+
+        FilesUploader(List<File> files, long totalSize, int threads,
+                boolean updateAsyncCache) {
+            super();
+            this.files = files;
+            this.threads = threads;
+            this.totalSize = totalSize;
+            this.updateAsyncCache = updateAsyncCache;
+        }
+
+        void addCurrentCount(int delta) {
+            currentCount.addAndGet(delta);
+        }
+
+        void addCurrentSize(long delta) {
+            currentSize.addAndGet(delta);
+        }
+
+        synchronized void setException(DataStoreException exception) {
+            exceptionRaised.getAndSet(true);
+            this.exception = exception;
+        }
+
+        boolean isExceptionRaised() {
+            return exceptionRaised.get();
+        }
+
+        void logProgress() {
+            LOG.info("Uploaded:  [{}/{}] files, [{}/{}] size data",
+                new Object[] { currentCount, files.size(), currentSize,
+                    totalSize });
+        }
+
+        void upload() throws DataStoreException {
+            long startTime = System.currentTimeMillis();
+            LOG.info(" Uploading [{}] using [{}] threads.", files.size(), threads);
+            ExecutorService executor = Executors.newFixedThreadPool(threads,
+                new NamedThreadFactory("backend-file-upload-worker"));
+            int partitionSize = files.size() / (threads);
+            int startIndex = 0;
+            int endIndex = partitionSize;
+            for (int i = 1; i <= threads; i++) {
+                List<File> partitionFileList = Collections.unmodifiableList(files.subList(
+                    startIndex, endIndex));
+                FileUploaderThread fut = new FileUploaderThread(
+                    partitionFileList, startIndex, endIndex, this,
+                    updateAsyncCache);
+                executor.execute(fut);
+
+                startIndex = endIndex;
+                if (i == (threads - 1)) {
+                    endIndex = files.size();
+                } else {
+                    endIndex = startIndex + partitionSize;
+                }
+            }
+            // This will make the executor accept no new threads
+            // and finish all existing threads in the queue
+            executor.shutdown();
+
+            try {
+                // Wait until all threads are finish
+                while (!isExceptionRaised()
+                    && !executor.awaitTermination(15, TimeUnit.SECONDS)) {
+                    logProgress();
+                }
+            } catch (InterruptedException ie) {
+
+            }
+            long endTime = System.currentTimeMillis();
+            LOG.info(
+                "Uploaded:  [{}/{}] files, [{}/{}] size data, time taken = [{}] sec",
+                new Object[] { currentCount, files.size(), currentSize,
+                    totalSize, ((endTime - startTime) / 1000) });
+            if (isExceptionRaised()) {
+                executor.shutdownNow(); // Cancel currently executing tasks
+                throw exception;
+            }
+        }
+
+    }
+
+    /**
+     * This class implements {@link Runnable} interface and uploads list of
+     * files from startIndex to endIndex to {@link Backend}
+     */
+    private class FileUploaderThread implements Runnable {
+        final List<File> files;
+
+        final FilesUploader filesUploader;
+
+        final int startIndex;
+
+        final int endIndex;
+
+        final boolean updateAsyncCache;
+
+        FileUploaderThread(List<File> files, int startIndex, int endIndex,
+                FilesUploader controller, boolean updateAsyncCache) {
+            super();
+            this.files = files;
+            this.filesUploader = controller;
+            this.startIndex = startIndex;
+            this.endIndex = endIndex;
+            this.updateAsyncCache = updateAsyncCache;
+        }
+
+        public void run() {
+            long time = System.currentTimeMillis();
+            LOG.debug(
+                "Thread [{}] : Uploading files from startIndex [{}] to endIndex [{}] both inclusive.",
+                new Object[] { Thread.currentThread().getName(), startIndex,
+                    (endIndex - 1) });
+            int uploadCount = 0;
+            long uploadSize = 0;
+            try {
+                for (File f : files) {
+
+                    if (filesUploader.isExceptionRaised()) {
+                        break;
+                    }
+                    String name = f.getName();
+                    LOG.debug("upload file [{}] ",name);
+                    if (!name.startsWith(TMP) && !name.endsWith(DS_STORE)
+                        && f.length() > 0) {
+                        uploadFileToBackEnd(f, updateAsyncCache);
+                    }
+                    uploadCount++;
+                    uploadSize += f.length();
+                    // update upload status at every 15 seconds.
+                    long now = System.currentTimeMillis();
+                    if (now > time + 15000) {
+                        filesUploader.addCurrentCount(uploadCount);
+                        filesUploader.addCurrentSize(uploadSize);
+                        uploadCount = 0;
+                        uploadSize = 0;
+                        time = now;
+                    }
+                }
+                // update final state.
+                filesUploader.addCurrentCount(uploadCount);
+                filesUploader.addCurrentSize(uploadSize);
+            } catch (DataStoreException e) {
+                if (!filesUploader.isExceptionRaised()) {
+                    filesUploader.setException(e);
+                }
+            }
+
+        }
+    }
+
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/CachingFDS.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/CachingFDS.java
new file mode 100644
index 0000000..1cfc50e
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/CachingFDS.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * {@link CachingDataStore} with {@link FSBackend}. It is performant 
+ * {@link DataStore} when {@link FSBackend} is hosted on network storage 
+ * (SAN or NAS). It leverages all caching capabilites of 
+ * {@link CachingDataStore}.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.util.Properties;
+
+public class CachingFDS extends CachingDataStore {
+    private Properties properties;
+
+    @Override
+    protected Backend createBackend() {
+        FSBackend backend = new FSBackend();
+        if (properties != null) {
+            backend.setProperties(properties);
+        }
+        return backend;
+    }
+
+    @Override
+    protected String getMarkerFile() {
+        return "fs.init.done";
+    }
+
+    /**
+     * Properties required to configure the S3Backend
+     */
+    public void setProperties(Properties properties) {
+        this.properties = properties;
+    }
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/DataIdentifier.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/DataIdentifier.java
new file mode 100644
index 0000000..0c0b36a
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/DataIdentifier.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.io.Serializable;
+
+/**
+ * Opaque data identifier used to identify records in a data store.
+ * All identifiers must be serializable and implement the standard
+ * object equality and hash code methods.
+ */
+public class DataIdentifier implements Serializable {
+
+    /**
+     * Serial version UID.
+     */
+    private static final long serialVersionUID = -9197191401131100016L;
+
+    /**
+     * Data identifier.
+     */
+    private final String identifier;
+
+    /**
+     * Creates a data identifier from the given string.
+     *
+     * @param identifier data identifier
+     */
+    public DataIdentifier(String identifier) {
+        this.identifier  = identifier;
+    }
+
+    //-------------------------------------------------------------< Object >
+
+    /**
+     * Returns the identifier string.
+     *
+     * @return identifier string
+     */
+    public String toString() {
+        return identifier;
+    }
+
+    /**
+     * Checks if the given object is a data identifier and has the same
+     * string representation as this one.
+     *
+     * @param object other object
+     * @return <code>true</code> if the given object is the same identifier,
+     *         <code>false</code> otherwise
+     */
+    public boolean equals(Object object) {
+        return (object instanceof DataIdentifier)
+            && identifier.equals(object.toString());
+    }
+
+    /**
+     * Returns the hash code of the identifier string.
+     *
+     * @return hash code
+     */
+    public int hashCode() {
+        return identifier.hashCode();
+    }
+
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/DataRecord.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/DataRecord.java
new file mode 100644
index 0000000..022b485
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/DataRecord.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.io.InputStream;
+
+/**
+ * Immutable data record that consists of a binary stream.
+ */
+public interface DataRecord {
+
+    /**
+     * Returns the identifier of this record.
+     *
+     * @return data identifier
+     */
+    DataIdentifier getIdentifier();
+
+    /**
+     * Returns a secure reference to this binary, or {@code null} if no such
+     * reference is available.
+     *
+     * @return binary reference, or {@code null}
+     */
+    String getReference();
+
+    /**
+     * Returns the length of the binary stream in this record.
+     *
+     * @return length of the binary stream
+     * @throws DataStoreException if the record could not be accessed
+     */
+    long getLength() throws DataStoreException;
+
+    /**
+     * Returns the the binary stream in this record.
+     *
+     * @return binary stream
+     * @throws DataStoreException if the record could not be accessed
+     */
+    InputStream getStream() throws DataStoreException;
+
+    /**
+     * Returns the last modified of the record.
+     * 
+     * @return last modified time of the binary stream
+     */
+    long getLastModified();
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/DataStore.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/DataStore.java
new file mode 100644
index 0000000..d714cf7
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/DataStore.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.io.InputStream;
+import java.util.Iterator;
+
+import javax.jcr.RepositoryException;
+
+/**
+ * Append-only store for binary streams. A data store consists of a number
+ * of identifiable data records that each contain a distinct binary stream.
+ * New binary streams can be added to the data store, but existing streams
+ * are never removed or modified.
+ * <p>
+ * A data store should be fully thread-safe, i.e. it should be possible to
+ * add and access data records concurrently. Optimally even separate processes
+ * should be able to concurrently access the data store with zero interprocess
+ * synchronization.
+ */
+public interface DataStore {
+
+    /**
+     * Check if a record for the given identifier exists, and return it if yes.
+     * If no record exists, this method returns null.
+     * 
+     * @param identifier data identifier
+     * @return the record if found, and null if not
+     * @throws DataStoreException if the data store could not be accessed
+     */
+    DataRecord getRecordIfStored(DataIdentifier identifier)
+            throws DataStoreException;
+
+    /**
+     * Returns the identified data record. The given identifier should be
+     * the identifier of a previously saved data record. Since records are
+     * never removed, there should never be cases where the identified record
+     * is not found. Abnormal cases like that are treated as errors and
+     * handled by throwing an exception.
+     *
+     * @param identifier data identifier
+     * @return identified data record
+     * @throws DataStoreException if the data store could not be accessed,
+     *                     or if the given identifier is invalid
+     */
+    DataRecord getRecord(DataIdentifier identifier) throws DataStoreException;
+
+    /**
+     * Returns the record that matches the given binary reference.
+     * Returns {@code null} if the reference is invalid, for example if it
+     * points to a record that does not exist.
+     *
+     * @param reference binary reference
+     * @return matching record, or {@code null}
+     * @throws DataStoreException if the data store could not be accessed
+     */
+    DataRecord getRecordFromReference(String reference)
+        throws DataStoreException;
+
+    /**
+     * Creates a new data record. The given binary stream is consumed and
+     * a binary record containing the consumed stream is created and returned.
+     * If the same stream already exists in another record, then that record
+     * is returned instead of creating a new one.
+     * <p>
+     * The given stream is consumed and <strong>not closed</strong> by this
+     * method. It is the responsibility of the caller to close the stream.
+     * A typical call pattern would be:
+     * <pre>
+     *     InputStream stream = ...;
+     *     try {
+     *         record = store.addRecord(stream);
+     *     } finally {
+     *         stream.close();
+     *     }
+     * </pre>
+     *
+     * @param stream binary stream
+     * @return data record that contains the given stream
+     * @throws DataStoreException if the data store could not be accessed
+     */
+    DataRecord addRecord(InputStream stream) throws DataStoreException;
+
+    /**
+     * From now on, update the modified date of an object even when accessing it.
+     * Usually, the modified date is only updated when creating a new object,
+     * or when a new link is added to an existing object. When this setting is enabled,
+     * even getLength() will update the modified date.
+     *
+     * @param before - update the modified date to the current time if it is older than this value
+     */
+    void updateModifiedDateOnAccess(long before);
+
+    /**
+     * Delete objects that have a modified date older than the specified date.
+     *
+     * @param min the minimum time
+     * @return the number of data records deleted
+     * @throws DataStoreException
+     */
+    int deleteAllOlderThan(long min) throws DataStoreException;
+
+    /**
+     * Get all identifiers.
+     *
+     * @return an iterator over all DataIdentifier objects
+     * @throws DataStoreException if the list could not be read
+     */
+    Iterator<DataIdentifier> getAllIdentifiers() throws DataStoreException;
+
+    /**
+     * Initialized the data store
+     *
+     * @param homeDir the home directory of the repository
+     * @throws RepositoryException
+     */
+    void init(String homeDir) throws RepositoryException;
+
+    /**
+     * Get the minimum size of an object that should be stored in this data store.
+     * Depending on the overhead and configuration, each store may return a different value.
+     *
+     * @return the minimum size in bytes
+     */
+    int getMinRecordLength();
+
+    /**
+     * Close the data store
+     *
+     * @throws DataStoreException if a problem occurred
+     */
+    void close() throws DataStoreException;
+
+    /**
+     * Clear the in-use list. This is only used for testing to make the the garbage collection
+     * think that objects are no longer in use.
+     */
+    void clearInUse();
+
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/DataStoreException.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/DataStoreException.java
similarity index 100%
rename from jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/DataStoreException.java
rename to jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/DataStoreException.java
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/DataStoreFactory.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/DataStoreFactory.java
new file mode 100644
index 0000000..390f4c5
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/DataStoreFactory.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import javax.jcr.RepositoryException;
+
+
+/**
+ * Factory interface for creating {@link DataStore} instances. Used
+ * to decouple the repository internals from the repository configuration
+ * mechanism.
+ *
+ * @since Jackrabbit 1.5
+ * @see <a href="https://issues.apache.org/jira/browse/JCR-1438">JCR-1438</a>
+ */
+public interface DataStoreFactory {
+
+    /**
+     * Creates, initializes, and returns a {@link DataStore} instance
+     * for use by the repository. Note that no information is passed from
+     * the client, so all required configuration information must be
+     * encapsulated in the factory.
+     *
+     * @return initialized data store
+     * @throws RepositoryException if the data store can not be created
+     */
+    DataStore getDataStore() throws RepositoryException;
+
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/FSBackend.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/FSBackend.java
new file mode 100644
index 0000000..d8f7e22
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/FSBackend.java
@@ -0,0 +1,496 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * File system {@link Backend} used with {@link CachingDataStore}. 
+ * The file system can be network storage.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.core.data.util.NamedThreadFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FSBackend implements Backend {
+
+    private Properties properties;
+
+    private String fsPath;
+
+    private CachingDataStore store;
+
+    private String homeDir;
+
+    private String config;
+
+    File fsPathDir;
+
+    private ThreadPoolExecutor asyncWriteExecuter;
+
+    public static final String FS_BACKEND_PATH = "fsBackendPath";
+
+    /**
+     * Logger instance.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(FSBackend.class);
+
+    /**
+     * The maximum last modified time resolution of the file system.
+     */
+    private static final int ACCESS_TIME_RESOLUTION = 2000;
+
+    @Override
+    public void init(CachingDataStore store, String homeDir, String config)
+                    throws DataStoreException {
+        Properties initProps = null;
+        // Check is configuration is already provided. That takes precedence
+        // over config provided via file based config
+        this.config = config;
+        if (this.properties != null) {
+            initProps = this.properties;
+        } else {
+            initProps = new Properties();
+            InputStream in = null;
+            try {
+                in = new FileInputStream(config);
+                initProps.load(in);
+            } catch (IOException e) {
+                throw new DataStoreException(
+                    "Could not initialize FSBackend from " + config, e);
+            } finally {
+                IOUtils.closeQuietly(in);
+            }
+            this.properties = initProps;
+        }
+        init(store, homeDir, initProps);
+
+    }
+
+    public void init(CachingDataStore store, String homeDir, Properties prop)
+                    throws DataStoreException {
+        this.store = store;
+        this.homeDir = homeDir;
+        this.fsPath = prop.getProperty(FS_BACKEND_PATH);
+        if (this.fsPath == null || "".equals(this.fsPath)) {
+            throw new DataStoreException("Could not initialize FSBackend from "
+                + config + ". [" + FS_BACKEND_PATH + "] property not found.");
+        }
+        fsPathDir = new File(this.fsPath);
+        if (fsPathDir.exists() && fsPathDir.isFile()) {
+            throw new DataStoreException("Can not create a directory "
+                + "because a file exists with the same name: " + this.fsPath);
+        }
+        if (!fsPathDir.exists()) {
+            boolean created = fsPathDir.mkdirs();
+            if (!created) {
+                throw new DataStoreException("Could not create directory: "
+                    + fsPathDir.getAbsolutePath());
+            }
+        }
+        asyncWriteExecuter = (ThreadPoolExecutor) Executors.newFixedThreadPool(
+            10, new NamedThreadFactory("fs-write-worker"));
+
+    }
+
+    @Override
+    public InputStream read(DataIdentifier identifier)
+                    throws DataStoreException {
+        File file = getFile(identifier);
+        try {
+            return new LazyFileInputStream(file);
+        } catch (IOException e) {
+            throw new DataStoreException("Error opening input stream of "
+                + file.getAbsolutePath(), e);
+        }
+    }
+
+    @Override
+    public long getLength(DataIdentifier identifier) throws DataStoreException {
+        File file = getFile(identifier);
+        if (file.isFile()) {
+            return file.length();
+        }
+        throw new DataStoreException("Could not length of dataIdentifier ["
+            + identifier + "]");
+    }
+
+    @Override
+    public long getLastModified(DataIdentifier identifier)
+                    throws DataStoreException {
+        long start = System.currentTimeMillis();
+        File f = getFile(identifier);
+        if (f.isFile()) {
+            return getLastModified(f);
+        }
+        LOG.info("getLastModified:Identifier [{}] not found. Took [{}] ms.",
+            identifier, (System.currentTimeMillis() - start));
+        throw new DataStoreException("Identifier [" + identifier
+            + "] not found.");
+    }
+
+    @Override
+    public void write(DataIdentifier identifier, File src)
+                    throws DataStoreException {
+        File dest = getFile(identifier);
+        synchronized (this) {
+            if (dest.exists()) {
+                long now = System.currentTimeMillis();
+                if (getLastModified(dest) < now + ACCESS_TIME_RESOLUTION) {
+                    setLastModified(dest, now + ACCESS_TIME_RESOLUTION);
+                }
+            } else {
+                try {
+                    FileUtils.copyFile(src, dest);
+                } catch (IOException ioe) {
+                    LOG.error("failed to copy [{}] to [{}]",
+                        src.getAbsolutePath(), dest.getAbsolutePath());
+                    throw new DataStoreException("Not able to write file ["
+                        + identifier + "]", ioe);
+                }
+            }
+        }
+
+    }
+
+    @Override
+    public void writeAsync(final DataIdentifier identifier, final File src,
+                    final AsyncUploadCallback callback)
+                    throws DataStoreException {
+        if (callback == null) {
+            throw new IllegalArgumentException(
+                "callback parameter cannot be null in asyncUpload");
+        }
+        asyncWriteExecuter.execute(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    write(identifier, src);
+                    callback.onSuccess(new AsyncUploadResult(identifier, src));
+                } catch (DataStoreException dse) {
+                    AsyncUploadResult res = new AsyncUploadResult(identifier,
+                        src);
+                    res.setException(dse);
+                    callback.onFailure(res);
+                }
+
+            }
+        });
+    }
+
+    @Override
+    public Iterator<DataIdentifier> getAllIdentifiers()
+                    throws DataStoreException {
+        ArrayList<File> files = new ArrayList<File>();
+        for (File file : fsPathDir.listFiles()) {
+            if (file.isDirectory()) { // skip top-level files
+                listRecursive(files, file);
+            }
+        }
+
+        ArrayList<DataIdentifier> identifiers = new ArrayList<DataIdentifier>();
+        for (File f : files) {
+            String name = f.getName();
+            identifiers.add(new DataIdentifier(name));
+        }
+        LOG.debug("Found " + identifiers.size() + " identifiers.");
+        return identifiers.iterator();
+    }
+
+    @Override
+    public boolean exists(DataIdentifier identifier, boolean touch)
+                    throws DataStoreException {
+        File file = getFile(identifier);
+        if (file.isFile()) {
+            if (touch) {
+                long now = System.currentTimeMillis();
+                setLastModified(file, now + ACCESS_TIME_RESOLUTION);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean exists(DataIdentifier identifier) throws DataStoreException {
+        return exists(identifier, false);
+    }
+
+    @Override
+    public void touch(DataIdentifier identifier, long minModifiedDate)
+                    throws DataStoreException {
+        File file = getFile(identifier);
+        long now = System.currentTimeMillis();
+        if (minModifiedDate > 0 && minModifiedDate > getLastModified(file)) {
+            setLastModified(file, now + ACCESS_TIME_RESOLUTION);
+        }
+    }
+
+    @Override
+    public void touchAsync(final DataIdentifier identifier,
+                    final long minModifiedDate,
+                    final AsyncTouchCallback callback)
+                    throws DataStoreException {
+        try {
+            if (callback == null) {
+                throw new IllegalArgumentException(
+                    "callback parameter cannot be null in touchAsync");
+            }
+            Thread.currentThread().setContextClassLoader(
+                getClass().getClassLoader());
+
+            asyncWriteExecuter.execute(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        touch(identifier, minModifiedDate);
+                        callback.onSuccess(new AsyncTouchResult(identifier));
+                    } catch (DataStoreException e) {
+                        AsyncTouchResult result = new AsyncTouchResult(
+                            identifier);
+                        result.setException(e);
+                        callback.onFailure(result);
+                    }
+                }
+            });
+        } catch (Exception e) {
+            callback.onAbort(new AsyncTouchResult(identifier));
+            throw new DataStoreException("Cannot touch the record "
+                + identifier.toString(), e);
+        }
+
+    }
+
+    @Override
+    public void close() throws DataStoreException {
+        asyncWriteExecuter.shutdownNow();
+
+    }
+
+    @Override
+    public Set<DataIdentifier> deleteAllOlderThan(long min)
+                    throws DataStoreException {
+        Set<DataIdentifier> deleteIdSet = new HashSet<DataIdentifier>(30);
+        for (File file : fsPathDir.listFiles()) {
+            if (file.isDirectory()) { // skip top-level files
+                deleteOlderRecursive(file, min, deleteIdSet);
+            }
+        }
+        return deleteIdSet;
+    }
+
+    @Override
+    public void deleteRecord(DataIdentifier identifier)
+                    throws DataStoreException {
+        File file = getFile(identifier);
+        synchronized (this) {
+            if (file.exists()) {
+                if (file.delete()) {
+                    deleteEmptyParentDirs(file);
+                } else {
+                    LOG.warn("Failed to delete file " + file.getAbsolutePath());
+                }
+            }
+        }
+    }
+
+    /**
+     * Properties used to configure the backend. If provided explicitly before
+     * init is invoked then these take precedence
+     * @param properties to configure S3Backend
+     */
+    public void setProperties(Properties properties) {
+        this.properties = properties;
+    }
+
+    /**
+     * Returns the identified file. This method implements the pattern used to
+     * avoid problems with too many files in a single directory.
+     * <p>
+     * No sanity checks are performed on the given identifier.
+     * @param identifier data identifier
+     * @return identified file
+     */
+    private File getFile(DataIdentifier identifier) {
+        String string = identifier.toString();
+        File file = this.fsPathDir;
+        file = new File(file, string.substring(0, 2));
+        file = new File(file, string.substring(2, 4));
+        file = new File(file, string.substring(4, 6));
+        return new File(file, string);
+    }
+
+    /**
+     * Set the last modified date of a file, if the file is writable.
+     * @param file the file
+     * @param time the new last modified date
+     * @throws DataStoreException if the file is writable but modifying the date
+     *             fails
+     */
+    private static void setLastModified(File file, long time)
+                    throws DataStoreException {
+        if (!file.setLastModified(time)) {
+            if (!file.canWrite()) {
+                // if we can't write to the file, so garbage collection will
+                // also not delete it
+                // (read only files or file systems)
+                return;
+            }
+            try {
+                // workaround for Windows: if the file is already open for
+                // reading
+                // (in this or another process), then setting the last modified
+                // date
+                // doesn't work - see also JCR-2872
+                RandomAccessFile r = new RandomAccessFile(file, "rw");
+                try {
+                    r.setLength(r.length());
+                } finally {
+                    r.close();
+                }
+            } catch (IOException e) {
+                throw new DataStoreException(
+                    "An IO Exception occurred while trying to set the last modified date: "
+                        + file.getAbsolutePath(), e);
+            }
+        }
+    }
+
+    /**
+     * Get the last modified date of a file.
+     * @param file the file
+     * @return the last modified date
+     * @throws DataStoreException if reading fails
+     */
+    private static long getLastModified(File file) throws DataStoreException {
+        long lastModified = file.lastModified();
+        if (lastModified == 0) {
+            throw new DataStoreException(
+                "Failed to read record modified date: "
+                    + file.getAbsolutePath());
+        }
+        return lastModified;
+    }
+
+    private void listRecursive(List<File> list, File file) {
+        File[] files = file.listFiles();
+        if (files != null) {
+            for (File f : files) {
+                if (f.isDirectory()) {
+                    listRecursive(list, f);
+                } else {
+                    list.add(f);
+                }
+            }
+        }
+    }
+
+    private void deleteEmptyParentDirs(File file) {
+        File parent = file.getParentFile();
+        try {
+            // Only iterate & delete if parent directory of the blob file is
+            // child
+            // of the base directory and if it is empty
+            while (FileUtils.directoryContains(fsPathDir, parent)) {
+                String[] entries = parent.list();
+                if (entries == null) {
+                    LOG.warn("Failed to list directory {}",
+                        parent.getAbsolutePath());
+                    break;
+                }
+                if (entries.length > 0) {
+                    break;
+                }
+                boolean deleted = parent.delete();
+                LOG.debug("Deleted parent [{}] of file [{}]: {}", new Object[] {
+                    parent, file.getAbsolutePath(), deleted });
+                parent = parent.getParentFile();
+            }
+        } catch (IOException e) {
+            LOG.warn("Error in parents deletion for " + file.getAbsoluteFile(),
+                e);
+        }
+    }
+
+    private void deleteOlderRecursive(File file, long min,
+                    Set<DataIdentifier> deleteIdSet) throws DataStoreException {
+        if (file.isFile() && file.exists() && file.canWrite()) {
+            synchronized (this) {
+                long lastModified;
+                try {
+                    lastModified = getLastModified(file);
+                } catch (DataStoreException e) {
+                    LOG.warn(
+                        "Failed to read modification date; file not deleted", e);
+                    // don't delete the file, since the lastModified date is
+                    // uncertain
+                    lastModified = min;
+                }
+                if (lastModified < min) {
+                    DataIdentifier id = new DataIdentifier(file.getName());
+                    if (store.confirmDelete(id)) {
+                        store.deleteFromCache(id);
+                        if (LOG.isInfoEnabled()) {
+                            LOG.info("Deleting old file "
+                                + file.getAbsolutePath() + " modified: "
+                                + new Timestamp(lastModified).toString()
+                                + " length: " + file.length());
+                        }
+                        if (file.delete()) {
+                            deleteIdSet.add(id);
+                        } else {
+                            LOG.warn("Failed to delete old file "
+                                + file.getAbsolutePath());
+                        }
+                    }
+                }
+            }
+        } else if (file.isDirectory()) {
+            File[] list = file.listFiles();
+            if (list != null) {
+                for (File f : list) {
+                    deleteOlderRecursive(f, min, deleteIdSet);
+                }
+            }
+
+            // JCR-1396: FileDataStore Garbage Collector and empty directories
+            // Automatic removal of empty directories (but not the root!)
+            synchronized (this) {
+                list = file.listFiles();
+                if (list != null && list.length == 0) {
+                    file.delete();
+                }
+            }
+        }
+    }
+
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/FileDataRecord.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/FileDataRecord.java
new file mode 100644
index 0000000..df0161d
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/FileDataRecord.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * Data record that is based on a normal file.
+ */
+public class FileDataRecord extends AbstractDataRecord {
+
+    /**
+     * The file that contains the binary stream.
+     */
+    private final File file;
+
+    /**
+     * Creates a data record based on the given identifier and file.
+     *
+     * @param identifier data identifier
+     * @param file file that contains the binary stream
+     */
+    public FileDataRecord(
+            AbstractDataStore store, DataIdentifier identifier, File file) {
+        super(store, identifier);
+        assert file.isFile();
+        this.file = file;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getLength() {
+        return file.length();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public InputStream getStream() throws DataStoreException {
+        try {
+            return new LazyFileInputStream(file);
+        } catch (IOException e) {
+            throw new DataStoreException("Error opening input stream of " + file.getAbsolutePath(), e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getLastModified() {
+        return file.lastModified();
+    }
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/FileDataStore.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/FileDataStore.java
new file mode 100644
index 0000000..5dc410c
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/FileDataStore.java
@@ -0,0 +1,507 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.lang.ref.WeakReference;
+import java.security.DigestOutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Simple file-based data store. Data records are stored as normal files
+ * named using a message digest of the contained binary stream.
+ *
+ * Configuration:
+ * <pre>
+ * <DataStore class="org.apache.jackrabbit.core.data.FileDataStore">
+ *     <param name="{@link #setPath(String) path}" value="/data/datastore"/>
+ *     <param name="{@link #setMinRecordLength(int) minRecordLength}" value="1024"/>
+ * &lt/DataStore>
+ * </pre>
+ * <p>
+ * If the directory is not set, the directory <repository home>/repository/datastore is used.
+ * <p>
+ * A three level directory structure is used to avoid placing too many
+ * files in a single directory. The chosen structure is designed to scale
+ * up to billions of distinct records.
+ * <p>
+ * This implementation relies on the underlying file system to support
+ * atomic O(1) move operations with {@link File#renameTo(File)}.
+ */
+public class FileDataStore extends AbstractDataStore
+        implements MultiDataStoreAware {
+
+    /**
+     * Logger instance
+     */
+    private static Logger log = LoggerFactory.getLogger(FileDataStore.class);
+
+    /**
+     * The digest algorithm used to uniquely identify records.
+     */
+    private static final String DIGEST = "SHA-1";
+
+    /**
+     * The default value for the minimum object size.
+     */
+    private static final int DEFAULT_MIN_RECORD_LENGTH = 100;
+
+    /**
+     * The maximum last modified time resolution of the file system.
+     */
+    private static final int ACCESS_TIME_RESOLUTION = 2000;
+
+    /**
+     * Name of the directory used for temporary files.
+     * Must be at least 3 characters.
+     */
+    private static final String TMP = "tmp";
+
+    /**
+     * The minimum modified date. If a file is accessed (read or write) with a modified date
+     * older than this value, the modified date is updated to the current time.
+     */
+    private volatile long minModifiedDate;
+
+    /**
+     * The directory that contains all the data record files. The structure
+     * of content within this directory is controlled by this class.
+     */
+    private File directory;
+
+    /**
+     * The name of the directory that contains all the data record files. The structure
+     * of content within this directory is controlled by this class.
+     */
+    private String path;
+
+    /**
+     * The minimum size of an object that should be stored in this data store.
+     */
+    private int minRecordLength = DEFAULT_MIN_RECORD_LENGTH;
+
+    /**
+     * All data identifiers that are currently in use are in this set until they are garbage collected.
+     */
+    protected Map<DataIdentifier, WeakReference<DataIdentifier>> inUse =
+        Collections.synchronizedMap(new WeakHashMap<DataIdentifier, WeakReference<DataIdentifier>>());
+
+    /**
+     * Initialized the data store.
+     * If the path is not set, <repository home>/repository/datastore is used.
+     * This directory is automatically created if it does not yet exist.
+     *
+     * @param homeDir
+     */
+    public void init(String homeDir) {
+        if (path == null) {
+            path = homeDir + "/repository/datastore";
+        }
+        directory = new File(path);
+        directory.mkdirs();
+    }
+
+    /**
+     * Get a data record for the given identifier.
+     *
+     * @param identifier the identifier
+     * @return the data record or null
+     */
+    public DataRecord getRecordIfStored(DataIdentifier identifier) throws DataStoreException {
+        File file = getFile(identifier);
+        if (!file.exists()) {
+            return null;
+        }
+        if (minModifiedDate != 0) {
+            // only check when running garbage collection
+            synchronized (this) {
+                if (getLastModified(file) < minModifiedDate) {
+                    setLastModified(file, System.currentTimeMillis() + ACCESS_TIME_RESOLUTION);
+                }
+            }
+        }
+        usesIdentifier(identifier);
+        return new FileDataRecord(this, identifier, file);
+    }
+
+    private void usesIdentifier(DataIdentifier identifier) {
+        inUse.put(identifier, new WeakReference<DataIdentifier>(identifier));
+    }
+
+    /**
+     * Creates a new data record.
+     * The stream is first consumed and the contents are saved in a temporary file
+     * and the SHA-1 message digest of the stream is calculated. If a
+     * record with the same SHA-1 digest (and length) is found then it is
+     * returned. Otherwise the temporary file is moved in place to become
+     * the new data record that gets returned.
+     *
+     * @param input binary stream
+     * @return data record that contains the given stream
+     * @throws DataStoreException if the record could not be created
+     */
+    public DataRecord addRecord(InputStream input) throws DataStoreException {
+        File temporary = null;
+        try {
+            temporary = newTemporaryFile();
+            DataIdentifier tempId = new DataIdentifier(temporary.getName());
+            usesIdentifier(tempId);
+            // Copy the stream to the temporary file and calculate the
+            // stream length and the message digest of the stream
+            long length = 0;
+            MessageDigest digest = MessageDigest.getInstance(DIGEST);
+            OutputStream output = new DigestOutputStream(
+                    new FileOutputStream(temporary), digest);
+            try {
+                length = IOUtils.copyLarge(input, output);
+            } finally {
+                output.close();
+            }
+            DataIdentifier identifier =
+                    new DataIdentifier(encodeHexString(digest.digest()));
+            File file;
+
+            synchronized (this) {
+                // Check if the same record already exists, or
+                // move the temporary file in place if needed
+                usesIdentifier(identifier);
+                file = getFile(identifier);
+                if (!file.exists()) {
+                    File parent = file.getParentFile();
+                    parent.mkdirs();
+                    if (temporary.renameTo(file)) {
+                        // no longer need to delete the temporary file
+                        temporary = null;
+                    } else {
+                        throw new IOException(
+                                "Can not rename " + temporary.getAbsolutePath()
+                                + " to " + file.getAbsolutePath()
+                                + " (media read only?)");
+                    }
+                } else {
+                    long now = System.currentTimeMillis();
+                    if (getLastModified(file) < now + ACCESS_TIME_RESOLUTION) {
+                        setLastModified(file, now + ACCESS_TIME_RESOLUTION);
+                    }
+                }
+                if (file.length() != length) {
+                    // Sanity checks on the record file. These should never fail,
+                    // but better safe than sorry...
+                    if (!file.isFile()) {
+                        throw new IOException("Not a file: " + file);
+                    }
+                    throw new IOException(DIGEST + " collision: " + file);
+                }
+            }
+            // this will also make sure that
+            // tempId is not garbage collected until here
+            inUse.remove(tempId);
+            return new FileDataRecord(this, identifier, file);
+        } catch (NoSuchAlgorithmException e) {
+            throw new DataStoreException(DIGEST + " not available", e);
+        } catch (IOException e) {
+            throw new DataStoreException("Could not add record", e);
+        } finally {
+            if (temporary != null) {
+                temporary.delete();
+            }
+        }
+    }
+
+    /**
+     * Returns the identified file. This method implements the pattern
+     * used to avoid problems with too many files in a single directory.
+     * <p>
+     * No sanity checks are performed on the given identifier.
+     *
+     * @param identifier data identifier
+     * @return identified file
+     */
+    private File getFile(DataIdentifier identifier) {
+        usesIdentifier(identifier);
+        String string = identifier.toString();
+        File file = directory;
+        file = new File(file, string.substring(0, 2));
+        file = new File(file, string.substring(2, 4));
+        file = new File(file, string.substring(4, 6));
+        return new File(file, string);
+    }
+
+    /**
+     * Returns a unique temporary file to be used for creating a new
+     * data record.
+     *
+     * @return temporary file
+     * @throws IOException
+     */
+    private File newTemporaryFile() throws IOException {
+        // the directory is already created in the init method
+        return File.createTempFile(TMP, null, directory);
+    }
+
+    public void updateModifiedDateOnAccess(long before) {
+        minModifiedDate = before;
+    }
+
+    public void deleteRecord(DataIdentifier identifier)
+			throws DataStoreException {
+        File file = getFile(identifier);
+        synchronized (this) {
+            if (file.exists()) {
+                if (file.delete()) {
+                    deleteEmptyParentDirs(file);
+                } else {
+                    log.warn("Failed to delete file " + file.getAbsolutePath());
+                }
+            }
+        }
+    }
+
+    private void deleteEmptyParentDirs(File file) {
+        File parent = file.getParentFile();
+        try {
+            // Only iterate & delete if parent directory of the blob file is child
+            // of the base directory and if it is empty
+            while (FileUtils.directoryContains(directory, parent)) {
+                String[] entries = parent.list();
+                if (entries == null) {
+                    log.warn("Failed to list directory {}", parent.getAbsolutePath());
+                    break;
+                }
+                if (entries.length > 0) {
+                    break;
+                }
+                boolean deleted = parent.delete();
+                log.debug("Deleted parent [{}] of file [{}]: {}",
+                        new Object[]{parent, file.getAbsolutePath(), deleted});
+                parent = parent.getParentFile();
+            }
+        } catch (IOException e) {
+            log.warn("Error in parents deletion for " + file.getAbsoluteFile(), e);
+        }
+    }
+
+    public int deleteAllOlderThan(long min) {
+        int count = 0;
+        for (File file : directory.listFiles()) {
+            if (file.isDirectory()) { // skip top-level files
+                count += deleteOlderRecursive(file, min);
+            }
+        }
+        return count;
+    }
+
+    private int deleteOlderRecursive(File file, long min) {
+        int count = 0;
+        if (file.isFile() && file.exists() && file.canWrite()) {
+            synchronized (this) {
+                long lastModified;
+                try {
+                    lastModified = getLastModified(file);
+                } catch (DataStoreException e) {
+                    log.warn("Failed to read modification date; file not deleted", e);
+                    // don't delete the file, since the lastModified date is uncertain
+                    lastModified = min;
+                }
+                if (lastModified < min) {
+                    DataIdentifier id = new DataIdentifier(file.getName());
+                    if (!inUse.containsKey(id)) {
+                        if (log.isInfoEnabled()) {
+                            log.info("Deleting old file " + file.getAbsolutePath() +
+                                    " modified: " + new Timestamp(lastModified).toString() +
+                                    " length: " + file.length());
+                        }
+                        if (!file.delete()) {
+                            log.warn("Failed to delete old file " + file.getAbsolutePath());
+                        }
+                        count++;
+                    }
+                }
+            }
+        } else if (file.isDirectory()) {
+            File[] list = file.listFiles();
+            if (list != null) {
+                for (File f: list) {
+                    count += deleteOlderRecursive(f, min);
+                }
+            }
+
+            // JCR-1396: FileDataStore Garbage Collector and empty directories
+            // Automatic removal of empty directories (but not the root!)
+            synchronized (this) {
+                list = file.listFiles();
+                if (list != null && list.length == 0) {
+                    file.delete();
+                }
+            }
+        }
+        return count;
+    }
+
+    private void listRecursive(List<File> list, File file) {
+        File[] files = file.listFiles();
+        if (files != null) {
+            for (File f : files) {
+                if (f.isDirectory()) {
+                    listRecursive(list, f);
+                } else {
+                    list.add(f);
+                }
+            }
+        }
+    }
+
+    public Iterator<DataIdentifier> getAllIdentifiers() {
+        ArrayList<File> files = new ArrayList<File>();
+        for (File file : directory.listFiles()) {
+            if (file.isDirectory()) { // skip top-level files
+                listRecursive(files, file);
+            }
+        }
+
+        ArrayList<DataIdentifier> identifiers = new ArrayList<DataIdentifier>();
+        for (File f: files) {
+            String name = f.getName();
+            identifiers.add(new DataIdentifier(name));
+        }
+        log.debug("Found " + identifiers.size() + " identifiers.");
+        return identifiers.iterator();
+    }
+
+    public void clearInUse() {
+        inUse.clear();
+    }
+
+    /**
+     * Get the name of the directory where this data store keeps the files.
+     *
+     * @return the full path name
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     * Set the name of the directory where this data store keeps the files.
+     *
+     * @param directoryName the path name
+     */
+    public void setPath(String directoryName) {
+        this.path = directoryName;
+    }
+
+    public int getMinRecordLength() {
+        return minRecordLength;
+    }
+
+    /**
+     * Set the minimum object length.
+     *
+     * @param minRecordLength the length
+     */
+    public void setMinRecordLength(int minRecordLength) {
+        this.minRecordLength = minRecordLength;
+    }
+
+    public void close() {
+        // nothing to do
+    }
+
+    //---------------------------------------------------------< protected >--
+
+    @Override
+    protected byte[] getOrCreateReferenceKey() throws DataStoreException {
+        File file = new File(directory, "reference.key");
+        try {
+            if (file.exists()) {
+                return FileUtils.readFileToByteArray(file);
+            } else {
+                byte[] key = super.getOrCreateReferenceKey();
+                FileUtils.writeByteArrayToFile(file, key);
+                return key;
+            }
+        } catch (IOException e) {
+            throw new DataStoreException(
+                    "Unable to access reference key file " + file.getPath(), e);
+        }
+    }
+
+    //-----------------------------------------------------------< private >--
+
+    /**
+     * Get the last modified date of a file.
+     *
+     * @param file the file
+     * @return the last modified date
+     * @throws DataStoreException if reading fails
+     */
+    private static long getLastModified(File file) throws DataStoreException {
+        long lastModified = file.lastModified();
+        if (lastModified == 0) {
+            throw new DataStoreException("Failed to read record modified date: " + file.getAbsolutePath());
+        }
+        return lastModified;
+    }
+
+    /**
+     * Set the last modified date of a file, if the file is writable.
+     *
+     * @param file the file
+     * @param time the new last modified date
+     * @throws DataStoreException if the file is writable but modifying the date fails
+     */
+    private static void setLastModified(File file, long time) throws DataStoreException {
+        if (!file.setLastModified(time)) {
+            if (!file.canWrite()) {
+                // if we can't write to the file, so garbage collection will also not delete it
+                // (read only files or file systems)
+                return;
+            }
+            try {
+                // workaround for Windows: if the file is already open for reading
+                // (in this or another process), then setting the last modified date
+                // doesn't work - see also JCR-2872
+                RandomAccessFile r = new RandomAccessFile(file, "rw");
+                try {
+                    r.setLength(r.length());
+                } finally {
+                    r.close();
+                }
+            } catch (IOException e) {
+                throw new DataStoreException("An IO Exception occurred while trying to set the last modified date: " + file.getAbsolutePath(), e);
+            }
+        }
+    }
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/LazyFileInputStream.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/LazyFileInputStream.java
new file mode 100644
index 0000000..a9c0489
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/LazyFileInputStream.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import org.apache.commons.io.input.AutoCloseInputStream;
+
+/**
+ * This input stream delays opening the file until the first byte is read, and
+ * closes and discards the underlying stream as soon as the end of input has
+ * been reached or when the stream is explicitly closed.
+ */
+public class LazyFileInputStream extends AutoCloseInputStream {
+
+    /**
+     * The file descriptor to use.
+     */
+    protected final FileDescriptor fd;
+
+    /**
+     * The file to read from.
+     */
+    protected final File file;
+
+    /**
+     * True if the input stream was opened. It is also set to true if the stream
+     * was closed without reading (to avoid opening the file after the stream
+     * was closed).
+     */
+    protected boolean opened;
+
+    /**
+     * Creates a new <code>LazyFileInputStream</code> for the given file. If the
+     * file is unreadable, a FileNotFoundException is thrown.
+     * The file is not opened until the first byte is read from the stream.
+     *
+     * @param file the file
+     * @throws java.io.FileNotFoundException
+     */
+    public LazyFileInputStream(File file)
+            throws FileNotFoundException {
+        super(null);
+        if (!file.canRead()) {
+            throw new FileNotFoundException(file.getPath());
+        }
+        this.file = file;
+        this.fd = null;
+    }
+
+    /**
+     * Creates a new <code>LazyFileInputStream</code> for the given file
+     * descriptor.
+     * The file is not opened until the first byte is read from the stream.
+     *
+     * @param fd
+     */
+    public LazyFileInputStream(FileDescriptor fd) {
+        super(null);
+        this.file = null;
+        this.fd = fd;
+    }
+
+    /**
+     * Creates a new <code>LazyFileInputStream</code> for the given file. If the
+     * file is unreadable, a FileNotFoundException is thrown.
+     *
+     * @param name
+     * @throws java.io.FileNotFoundException
+     */
+    public LazyFileInputStream(String name) throws FileNotFoundException {
+        this(new File(name));
+    }
+
+    /**
+     * Open the stream if required.
+     *
+     * @throws java.io.IOException
+     */
+    protected void open() throws IOException {
+        if (!opened) {
+            opened = true;
+            if (fd != null) {
+                in = new FileInputStream(fd);
+            } else {
+                in = new FileInputStream(file);
+            }
+        }
+    }
+
+    public int read() throws IOException {
+        open();
+        return super.read();
+    }
+
+    public int available() throws IOException {
+        open();
+        return super.available();
+    }
+
+    public void close() throws IOException {
+        // make sure the file is not opened afterwards
+        opened = true;
+        
+        // only close the file if it was in fact opened
+        if (in != null) {
+            super.close();
+        }
+    }
+
+    public synchronized void reset() throws IOException {
+        open();
+        super.reset();
+    }
+
+    public boolean markSupported() {
+        try {
+            open();
+        } catch (IOException e) {
+            throw new IllegalStateException(e.toString());
+        }
+        return super.markSupported();
+    }
+
+    public synchronized void mark(int readlimit) {
+        try {
+            open();
+        } catch (IOException e) {
+            throw new IllegalStateException(e.toString());
+        }
+        super.mark(readlimit);
+    }
+
+    public long skip(long n) throws IOException {
+        open();
+        return super.skip(n);
+    }
+
+    public int read(byte[] b) throws IOException {
+        open();
+        return super.read(b, 0, b.length);
+    }
+
+    public int read(byte[] b, int off, int len) throws IOException {
+        open();
+        return super.read(b, off, len);
+    }
+
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/LocalCache.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/LocalCache.java
new file mode 100644
index 0000000..0606bbf
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/LocalCache.java
@@ -0,0 +1,643 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.core.data;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.util.TransientFileFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class implements a LRU cache used by {@link CachingDataStore}. If cache
+ * size exceeds limit, this cache goes in purge mode. In purge mode any
+ * operation to cache is no-op. After purge cache size would be less than
+ * cachePurgeResizeFactor * maximum size.
+ */
+public class LocalCache {
+
+    /**
+     * Logger instance.
+     */
+    static final Logger LOG = LoggerFactory.getLogger(LocalCache.class);
+
+    /**
+     * The file names of the files that need to be deleted.
+     */
+    final Set<String> toBeDeleted = new HashSet<String>();
+
+    /**
+     * The filename Vs file size LRU cache.
+     */
+    LRUCache cache;
+
+    /**
+     * The directory where the files are created.
+     */
+    private final File directory;
+
+    /**
+     * The directory where tmp files are created.
+     */
+    private final File tmp;
+
+    /**
+     * If true cache is in purgeMode and not available. All operation would be
+     * no-op.
+     */
+    private volatile boolean purgeMode;
+    
+    private AsyncUploadCache asyncUploadCache;
+
+    /**
+     * Build LRU cache of files located at 'path'. It uses lastModified property
+     * of file to build LRU cache. If cache size exceeds limit size, this cache
+     * goes in purge mode. In purge mode any operation to cache is no-op.
+     * 
+     * @param path file system path
+     * @param tmpPath temporary directory used by cache.
+     * @param maxSizeInBytes maximum size of cache.
+     * @param cachePurgeTrigFactor factor which triggers cache to purge mode.
+     * That is if current size exceed (cachePurgeTrigFactor * maxSizeInBytes), the
+     * cache will go in auto-purge mode.
+     * @param cachePurgeResizeFactor after cache purge size of cache will be
+     * just less (cachePurgeResizeFactor * maxSizeInBytes).
+     * @param asyncUploadCache {@link AsyncUploadCache}
+     */
+    public LocalCache(String path, String tmpPath, long maxSizeInBytes, double cachePurgeTrigFactor,
+            double cachePurgeResizeFactor, AsyncUploadCache asyncUploadCache) {
+        directory = new File(path);
+        tmp = new File(tmpPath);
+        LOG.info(
+            "cachePurgeTrigFactor =[{}], cachePurgeResizeFactor =[{}],  " +
+            "cachePurgeTrigFactorSize =[{}], cachePurgeResizeFactorSize =[{}]",
+            new Object[] { cachePurgeTrigFactor, cachePurgeResizeFactor,
+                (cachePurgeTrigFactor * maxSizeInBytes), 
+                (cachePurgeResizeFactor * maxSizeInBytes) });
+        cache = new LRUCache(maxSizeInBytes, cachePurgeTrigFactor, cachePurgeResizeFactor);
+        this.asyncUploadCache = asyncUploadCache;
+        new Thread(new CacheBuildJob()).start();
+    }
+
+    /**
+     * Store an item in the cache and return the input stream. If cache is in
+     * purgeMode or file doesn't exists, inputstream from a
+     * {@link TransientFileFactory#createTransientFile(String, String, File)} is
+     * returned. Otherwise inputStream from cached file is returned. This method
+     * doesn't close the incoming inputstream.
+     * 
+     * @param fileName the key of cache.
+     * @param in {@link InputStream}
+     * @return the (new) input stream.
+     */
+    public InputStream store(String fileName, final InputStream in)
+            throws IOException {
+        fileName = fileName.replace("\\", "/");
+        File f = getFile(fileName);
+        long length = 0;
+        if (!f.exists() || isInPurgeMode()) {
+            OutputStream out = null;
+            File transFile = null;
+            try {
+                TransientFileFactory tff = TransientFileFactory.getInstance();
+                transFile = tff.createTransientFile("s3-", "tmp", tmp);
+                out = new BufferedOutputStream(new FileOutputStream(transFile));
+                length = IOUtils.copyLarge(in, out);
+            } finally {
+                IOUtils.closeQuietly(out);
+            }
+            // rename the file to local fs cache
+            if (canAdmitFile(length)
+                && (f.getParentFile().exists() || f.getParentFile().mkdirs())
+                && transFile.renameTo(f) && f.exists()) {
+                if (transFile.exists() && transFile.delete()) {
+                    LOG.info("tmp file [{}] not deleted successfully",
+                        transFile.getAbsolutePath());
+                }
+                transFile = null;
+                LOG.debug(
+                    "file [{}] doesn't exists. adding to local cache using inputstream.",
+                    fileName);
+                cache.put(fileName, f.length());
+            } else {
+                LOG.debug(
+                    "file [{}] doesn't exists. returning transient file [{}].",
+                    fileName, transFile.getAbsolutePath());
+                f = transFile;
+            }
+        } else {
+            f.setLastModified(System.currentTimeMillis());
+            LOG.debug(
+                "file [{}]  exists. adding to local cache using inputstream.",
+                fileName);
+            cache.put(fileName, f.length());
+        }
+        tryPurge();
+        return new LazyFileInputStream(f);
+    }
+
+    /**
+     * Store an item along with file in cache. Cache size is increased by
+     * {@link File#length()} If file already exists in cache,
+     * {@link File#setLastModified(long)} is updated with current time.
+     * 
+     * @param fileName the key of cache.
+     * @param src file to be added to cache.
+     */
+    public File store(String fileName, final File src) {
+        try {
+            return store(fileName, src, false).getFile();
+        } catch (IOException ioe) {
+            LOG.warn("Exception in addding file [" + fileName + "] to local cache.", ioe);
+        }
+        return null;
+    }
+
+    /**
+     * This method add file to {@link LocalCache} and tries that file can be
+     * added to {@link AsyncUploadCache}. If file is added to
+     * {@link AsyncUploadCache} successfully, it sets
+     * {@link AsyncUploadCacheResult#setAsyncUpload(boolean)} to true.
+     *
+     * @param fileName name of the file.
+     * @param src source file.
+     * @param tryForAsyncUpload If true it tries to add fileName to
+     *            {@link AsyncUploadCache}
+     * @return {@link AsyncUploadCacheResult}. This method sets
+     *         {@link AsyncUploadCacheResult#setAsyncUpload(boolean)} to true, if
+     *         fileName is added to {@link AsyncUploadCache} successfully else
+     *         it sets {@link AsyncUploadCacheResult#setAsyncUpload(boolean)} to
+     *         false. {@link AsyncUploadCacheResult#getFile()} contains cached
+     *         file, if it is added to {@link LocalCache} or original file.
+     * @throws IOException
+     */
+    public AsyncUploadCacheResult store(String fileName, File src,
+            boolean tryForAsyncUpload) throws IOException {
+        fileName = fileName.replace("\\", "/");
+        File dest = getFile(fileName);
+        File parent = dest.getParentFile();
+        AsyncUploadCacheResult result = new AsyncUploadCacheResult();
+        result.setFile(src);
+        result.setAsyncUpload(false);
+        boolean destExists = false;
+        if ((destExists = dest.exists())
+            || (src.exists() && !dest.exists() && !src.equals(dest)
+                && canAdmitFile(src.length())
+                && (parent.exists() || parent.mkdirs()) && (src.renameTo(dest)))) {
+            if (destExists) {
+                dest.setLastModified(System.currentTimeMillis());
+            }
+            LOG.debug("file [{}] moved to [{}] ", src.getAbsolutePath(), dest.getAbsolutePath());
+            LOG.debug(
+                "file [{}]  exists= [{}] added to local cache, isLastModified [{}]",
+                new Object[] { dest.getAbsolutePath(), dest.exists(),
+                    destExists });
+            
+            cache.put(fileName, dest.length());
+            result.setFile(dest);
+            if (tryForAsyncUpload) {
+                result.setAsyncUpload(asyncUploadCache.add(fileName).canAsyncUpload());
+            }
+        } else {
+            LOG.info("file [{}] exists= [{}] not added to local cache.",
+                fileName, destExists);
+        }
+        tryPurge();
+        return result;
+    }
+    /**
+     * Return the inputstream from from cache, or null if not in the cache.
+     * 
+     * @param fileName name of file.
+     * @return  stream or null.
+     */
+    public InputStream getIfStored(String fileName) throws IOException {
+        File file = getFileIfStored(fileName);
+        return file == null ? null : new LazyFileInputStream(file);
+    }
+
+    public File getFileIfStored(String fileName) throws IOException {
+        fileName = fileName.replace("\\", "/");
+        File f = getFile(fileName);
+        // return file in purge mode = true and file present in asyncUploadCache
+        // as asyncUploadCache's files will be not be deleted in cache purge.
+        if (!f.exists() || (isInPurgeMode() && !asyncUploadCache.hasEntry(fileName, false))) {
+            LOG.debug(
+                "getFileIfStored returned: purgeMode=[{}], file=[{}] exists=[{}]",
+                new Object[] { isInPurgeMode(), f.getAbsolutePath(), f.exists() });
+            return null;
+        } else {
+            // touch entry in LRU caches
+            f.setLastModified(System.currentTimeMillis());
+            cache.get(fileName);
+            return f;
+        }
+    }
+
+    /**
+     * Delete file from cache. Size of cache is reduced by file length. The
+     * method is no-op if file doesn't exist in cache.
+     * 
+     * @param fileName file name that need to be removed from cache.
+     */
+    public void delete(String fileName) {
+        if (isInPurgeMode()) {
+            LOG.debug("purgeMode true :delete returned");
+            return;
+        }
+        fileName = fileName.replace("\\", "/");
+        cache.remove(fileName);
+    }
+
+    /**
+     * Returns length of file if exists in cache else returns null.
+     * @param fileName name of the file.
+     */
+    public Long getFileLength(String fileName) {
+        Long length = null;
+        try {
+            length = cache.get(fileName);
+            if( length == null ) {
+                File f = getFileIfStored(fileName);
+                if (f != null) {
+                    length = f.length();
+                }
+            }
+        } catch (IOException ignore) {
+
+        }
+        return length;
+    }
+
+    /**
+     * Close the cache. Cache maintain set of files which it was not able to
+     * delete successfully. This method will an attempt to delete all
+     * unsuccessful delete files.
+     */
+    public void close() {
+        LOG.debug("close");
+        deleteOldFiles();
+    }
+
+    /**
+     * Check if cache can admit file of given length.
+     * @param length of the file.
+     * @return true if yes else return false.
+     */
+    private boolean canAdmitFile(final long length) {
+      //order is important here
+        boolean value = !isInPurgeMode() && (cache.canAdmitFile(length));
+        if (!value) {
+            LOG.debug("cannot admit file of length=[{}] and currentSizeInBytes=[{}] ",
+                length, cache.currentSizeInBytes);
+        }
+        return value;
+    }
+
+    /**
+     * Return true if cache is in purge mode else return false.
+     */
+    synchronized boolean isInPurgeMode() {
+        return purgeMode;
+    }
+
+    /**
+     * Set purge mode. If set to true all cache operation will be no-op. If set
+     * to false, all operations to cache are available.
+     * 
+     * @param purgeMode purge mode
+     */
+    synchronized void setPurgeMode(final boolean purgeMode) {
+        this.purgeMode = purgeMode;
+    }
+
+    File getFile(final String fileName) {
+        return new File(directory, fileName);
+    }
+
+    private void deleteOldFiles() {
+        int initialSize = toBeDeleted.size();
+        int count = 0;
+        for (String fileName : new ArrayList<String>(toBeDeleted)) {
+            fileName = fileName.replace("\\", "/");
+            if( cache.remove(fileName) != null) {
+                count++;
+            }
+        }
+        LOG.info("deleted [{}]/[{}] files.", count, initialSize);
+    }
+
+    /**
+     * This method tries to delete a file. If it is not able to delete file due
+     * to any reason, it add it toBeDeleted list.
+     * 
+     * @param fileName name of the file which will be deleted.
+     * @return true if this method deletes file successfuly else return false.
+     */
+    boolean tryDelete(final String fileName) {
+        LOG.debug("try deleting file [{}]", fileName);
+        File f = getFile(fileName);
+        if (f.exists() && f.delete()) {
+            LOG.info("File [{}]  deleted successfully", f.getAbsolutePath());
+            toBeDeleted.remove(fileName);
+            while (true) {
+                f = f.getParentFile();
+                if (f.equals(directory) || f.list().length > 0) {
+                    break;
+                }
+                // delete empty parent folders (except the main directory)
+                f.delete();
+            }
+            return true;
+        } else if (f.exists()) {
+            LOG.info("not able to delete file [{}]", f.getAbsolutePath());
+            toBeDeleted.add(fileName);
+            return false;
+        }
+        return true;
+    }
+
+    static int maxSizeElements(final long bytes) {
+        // after a CQ installation, the average item in
+        // the data store is about 52 KB
+        int count = (int) (bytes / 65535);
+        count = Math.max(1024, count);
+        count = Math.min(64 * 1024, count);
+        return count;
+    }
+    
+    /**
+     * This method tries purging of local cache. It checks if local cache
+     * has exceeded the defined limit then it triggers purge cache job in a
+     * seperate thread.
+     */
+    synchronized void tryPurge() {
+        if (!isInPurgeMode()
+            && cache.currentSizeInBytes > cache.cachePurgeTrigSize) {
+            setPurgeMode(true);
+            LOG.info(
+                "cache.entries = [{}], currentSizeInBytes=[{}]  exceeds cachePurgeTrigSize=[{}]",
+                new Object[] { cache.size(), cache.currentSizeInBytes,
+                    cache.cachePurgeTrigSize });
+            new Thread(new PurgeJob()).start();
+        } else {
+            LOG.debug(
+                "currentSizeInBytes=[{}],cachePurgeTrigSize=[{}], isInPurgeMode =[{}]",
+                new Object[] { cache.currentSizeInBytes,
+                    cache.cachePurgeTrigSize, isInPurgeMode() });
+        }
+    }
+
+    /**
+     * A LRU based extension {@link LinkedHashMap}. The key is file name and
+     * value is length of file.
+     */
+    private class LRUCache extends LinkedHashMap<String, Long> {
+        private static final long serialVersionUID = 1L;
+
+        volatile long currentSizeInBytes;
+
+        final long maxSizeInBytes;
+
+        final long cachePurgeResize;
+        
+        final long cachePurgeTrigSize;
+
+        LRUCache(final long maxSizeInBytes,
+                final double cachePurgeTrigFactor,
+                final double cachePurgeResizeFactor) {
+            super(maxSizeElements(maxSizeInBytes), (float) 0.75, true);
+            this.maxSizeInBytes = maxSizeInBytes;
+            this.cachePurgeTrigSize = new Double(cachePurgeTrigFactor
+                * maxSizeInBytes).longValue();
+            this.cachePurgeResize = new Double(cachePurgeResizeFactor
+                * maxSizeInBytes).longValue();
+        }
+
+        /**
+         * Overridden {@link Map#remove(Object)} to delete corresponding file
+         * from file system.
+         */
+        @Override
+        public synchronized Long remove(final Object key) {
+            String fileName = (String) key;
+            fileName = fileName.replace("\\", "/");
+            try {
+                // not removing file from local cache, if there is in progress
+                // async upload on it.
+                if (asyncUploadCache.hasEntry(fileName, false)) {
+                    LOG.info(
+                        "AsyncUploadCache upload contains file [{}]. Not removing it from LocalCache.",
+                        fileName);
+                    return null;
+                }
+            } catch (IOException e) {
+                LOG.debug("error: ", e);
+                return null;
+            }
+            Long flength = null;
+            if (tryDelete(fileName)) {
+                flength = super.remove(key);
+                if (flength != null) {
+                    LOG.debug("cache entry [{}], with size [{}] removed.",
+                        fileName, flength);
+                    currentSizeInBytes -= flength.longValue();
+                }
+            } else if (!getFile(fileName).exists()) {
+                // second attempt. remove from cache if file doesn't exists
+                flength = super.remove(key);
+                if (flength != null) {
+                    LOG.debug(
+                        "file not exists. cache entry [{}], with size [{}] removed.",
+                        fileName, flength);
+                    currentSizeInBytes -= flength.longValue();
+                }
+            } else {
+                LOG.info("not able to remove cache entry [{}], size [{}]", key,
+                    super.get(key));
+            }
+            return flength;
+        }
+
+        @Override
+        public Long put(final String fileName, final Long value) {
+            if( isInPurgeMode()) {
+                LOG.debug("cache is purge mode: put is no-op");
+                return null;
+            }
+            synchronized (this) {
+                Long oldValue = cache.get(fileName);
+                if (oldValue == null) {
+                    long flength = value.longValue();
+                    currentSizeInBytes += flength;
+                    return super.put(fileName.replace("\\", "/"), value);
+                }
+                toBeDeleted.remove(fileName);
+                return oldValue;
+            }
+        }
+        
+        @Override
+        public Long get(Object key) {
+            if( isInPurgeMode()) {
+                LOG.debug("cache is purge mode: get is no-op");
+                return null;
+            }
+            synchronized (this) {
+                return super.get(key);
+            }
+        }
+        
+        /**
+         * This method check if cache can admit file of given length. 
+         * @param length length of file.
+         * @return true if cache size + length is less than maxSize.
+         */
+        synchronized boolean canAdmitFile(final long length) {
+            return cache.currentSizeInBytes + length < cache.maxSizeInBytes;
+        }
+    }
+
+    /**
+     * This class performs purging of local cache. It implements
+     * {@link Runnable} and should be invoked in a separate thread.
+     */
+    private class PurgeJob implements Runnable {
+        public PurgeJob() {
+            // TODO Auto-generated constructor stub
+        }
+
+        /**
+         * This method purges local cache till its size is less than
+         * cacheResizefactor * maxSize
+         */
+        @Override
+        public void run() {
+            try {
+                synchronized (cache) {
+                    // first try to delete toBeDeleted files
+                    int initialSize = cache.size();
+                    LOG.info(" cache purge job started. initial cache entries = [{}]", initialSize);
+                    for (String fileName : new ArrayList<String>(toBeDeleted)) {
+                        cache.remove(fileName);
+                    }
+                    Iterator<Map.Entry<String, Long>> itr = cache.entrySet().iterator();
+                    while (itr.hasNext()) {
+                        Map.Entry<String, Long> entry = itr.next();
+                        if (entry.getKey() != null) {
+                            if (cache.currentSizeInBytes > cache.cachePurgeResize) {
+                                cache.remove(entry.getKey());
+                                itr = cache.entrySet().iterator();
+                            } else {
+                                break;
+                            }
+                        }
+                    }
+                    LOG.info(
+                        " cache purge job completed: cleaned [{}] files and currentSizeInBytes = [{}]",
+                        (initialSize - cache.size()), cache.currentSizeInBytes);
+                }
+            } catch (Exception e) {
+                LOG.error("error in purge jobs:", e);
+            } finally {
+                setPurgeMode(false);
+            }
+        }
+    }
+    
+    /**
+     * This class implements {@link Runnable} interface to build LRU cache
+     * asynchronously.
+     */
+    private class CacheBuildJob implements Runnable {
+
+        
+        public void run() {
+            long startTime = System.currentTimeMillis();
+            ArrayList<File> allFiles = new ArrayList<File>();
+            Iterator<File> it = FileUtils.iterateFiles(directory, null, true);
+            while (it.hasNext()) {
+                File f = it.next();
+                allFiles.add(f);
+            }
+            long t1 = System.currentTimeMillis();
+            LOG.debug("Time taken to recursive [{}] took [{}] sec",
+                allFiles.size(), ((t1 - startTime) / 1000));
+
+            String dataStorePath = directory.getAbsolutePath();
+            // convert to java path format
+            dataStorePath = dataStorePath.replace("\\", "/");
+            LOG.info("directoryPath = " + dataStorePath);
+
+            String tmpPath = tmp.getAbsolutePath();
+            tmpPath = tmpPath.replace("\\", "/");
+            LOG.debug("tmp path [{}]", tmpPath); 
+            long time = System.currentTimeMillis();
+            int count = 0;
+            for (File f : allFiles) {
+                if (f.exists()) {
+                    count++;
+                    String name = f.getPath();
+                    String filePath = f.getAbsolutePath();
+                    // convert to java path format
+                    name = name.replace("\\", "/");
+                    filePath = filePath.replace("\\", "/");
+                    // skipped any temp file
+                    if(filePath.startsWith(tmpPath) ) {
+                        LOG.info    ("tmp file [{}] skipped ", filePath);
+                        continue;
+                    }
+                    if (filePath.startsWith(dataStorePath)) {
+                        name = filePath.substring(dataStorePath.length());
+                    }
+                    if (name.startsWith("/") || name.startsWith("\\")) {
+                        name = name.substring(1);
+                    }
+                    store(name, f);
+                    long now = System.currentTimeMillis();
+                    if (now > time + 10000) {
+                        LOG.info("Processed {" + (count) + "}/{" + allFiles.size() + "}");
+                        time = now;
+                    }
+                }
+            }
+            LOG.debug(
+                "Processed [{}]/[{}], currentSizeInBytes = [{}], maxSizeInBytes = [{}], cache.filecount = [{}]",
+                new Object[] { count, allFiles.size(),
+                    cache.currentSizeInBytes, cache.maxSizeInBytes,
+                    cache.size() });
+            long t3 = System.currentTimeMillis();
+            LOG.info("Time to build cache of  [{}] files took [{}] sec",
+                allFiles.size(), ((t3 - startTime) / 1000));
+        }
+    }
+}
+
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/MultiDataStore.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/MultiDataStore.java
new file mode 100644
index 0000000..cb6106d
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/MultiDataStore.java
@@ -0,0 +1,722 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.core.fs.FileSystem;
+import org.apache.jackrabbit.core.fs.FileSystemException;
+import org.apache.jackrabbit.core.fs.FileSystemResource;
+import org.apache.jackrabbit.core.fs.local.LocalFileSystem;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A MultiDataStore can handle two independent DataStores.
+ * <p>
+ * <b>Attention:</b> You will lost the global single instance mechanism !
+ * </p>
+ * It can be used if you have two storage systems. One for fast access and a
+ * other one like a archive DataStore on a slower storage system. All Files will
+ * be added to the primary DataStore. On read operations first the primary
+ * dataStore will be used and if no Record is found the archive DataStore will
+ * be used. The GarabageCollector will only remove files from the archive
+ * DataStore.
+ * <p>
+ * The internal MoveDataTask will be started automatically and could be
+ * configured with the following properties.
+ * <p>
+ * The Configuration:
+ * 
+ * <pre>
+ * <DataStore class="org.apache.jackrabbit.core.data.MultiDataStore">
+ *     <param name="{@link #setMaxAge(int) maxAge}" value="60"/>
+ *     <param name="{@link #setMoveDataTaskSleep(int) moveDataTaskSleep}" value="604800"/>
+ *     <param name="{@link #setMoveDataTaskFirstRunHourOfDay(int) moveDataTaskFirstRunHourOfDay}" value="1"/>
+ *     <param name="{@link #setSleepBetweenRecords(long) sleepBetweenRecords}" value="100"/>
+ *     <param name="{@link #setDelayedDelete(boolean) delayedDelete}" value="false"/>
+ *     <param name="{@link #setDelayedDeleteSleep(long) delayedDeleteSleep}" value="86400"/>
+ *     <param name="primary" value="org.apache.jackrabbit.core.data.db.DbDataStore">
+ *        <param .../>
+ *     </param>
+ *     <param name="archive" value="org.apache.jackrabbit.core.data.FileDataStore">
+ *        <param .../>
+ *     </param>
+ * &lt/DataStore>
+ * </pre>
+ * 
+ * <ul>
+ * <li><code>maxAge</code>: defines how many days the content will reside in the
+ * primary data store. DataRecords that have been added before this time span
+ * will be moved to the archive data store. (default = <code>60</code>)</li>
+ * <li><code>moveDataTaskSleep</code>: specifies the sleep time of the
+ * moveDataTaskThread in seconds. (default = 60 * 60 * 24 * 7, which equals 7
+ * days)</li>
+ * <li><code>moveDataTaskNextRunHourOfDay</code>: specifies the hour at which
+ * the moveDataTaskThread initiates its first run (default = <code>1</code>
+ * which means 01:00 at night)</li>
+ * <li><code>sleepBetweenRecords</code>: specifies the delay in milliseconds
+ * between scanning data records (default = <code>100</code>)</li>
+ * <li><code>delayedDelete</code>: its possible to delay the delete operation on
+ * the primary data store. The DataIdentifiers will be written to a temporary
+ * file. The file will be processed after a defined sleep (see
+ * <code>delayedDeleteSleep</code>) It's useful if you like to create a snapshot
+ * of the primary data store backend in the meantime before the data will be
+ * deleted. (default = <code>false</code>)</li>
+ * <li><code>delayedDeleteSleep</code>: specifies the sleep time of the
+ * delayedDeleteTaskThread in seconds. (default = 60 * 60 * 24, which equals 1
+ * day). This means the delayed delete from the primary data store will be
+ * processed after one day.</li>
+ * </ul>
+ */
+public class MultiDataStore implements DataStore {
+
+    /**
+     * Logger instance
+     */
+    private static Logger log = LoggerFactory.getLogger(MultiDataStore.class);
+
+    private DataStore primaryDataStore;
+    private DataStore archiveDataStore;
+
+    /**
+     * Max Age in days.
+     */
+    private int maxAge = 60;
+
+    /**
+     * ReentrantLock that is used while the MoveDataTask is running.
+     */
+    private ReentrantLock moveDataTaskLock = new ReentrantLock();
+    private boolean moveDataTaskRunning = false;
+    private Thread moveDataTaskThread;
+
+    /**
+     * The sleep time in seconds of the MoveDataTask, 7 day default.
+     */
+    private int moveDataTaskSleep = 60 * 60 * 24 * 7;
+
+    /**
+     * Indicates when the next run of the move task is scheduled. The first run
+     * is scheduled by default at 01:00 hours.
+     */
+    private Calendar moveDataTaskNextRun = Calendar.getInstance();
+
+    /**
+     * Its possible to delay the delete operation on the primary data store
+     * while move task is running. The delete will be executed after defined
+     * delayDeleteSleep.
+     */
+    private boolean delayedDelete = false;
+
+    /**
+     * The sleep time in seconds to delay remove operation on the primary data
+     * store, 1 day default.
+     */
+    private long delayedDeleteSleep = 60 * 60 * 24;
+
+    /**
+     * File that holds the data identifiers if delayDelete is enabled.
+     */
+    private FileSystemResource identifiersToDeleteFile = null;
+
+    private Thread deleteDelayedIdentifiersTaskThread;
+
+    /**
+     * Name of the file which holds the identifiers if deleayed delete is
+     * enabled
+     */
+    private final String IDENTIFIERS_TO_DELETE_FILE_KEY = "identifiersToDelete";
+
+    /**
+     * The delay time in milliseconds between scanning data records, 100
+     * default.
+     */
+    private long sleepBetweenRecords = 100;
+
+    {
+        if (moveDataTaskNextRun.get(Calendar.HOUR_OF_DAY) >= 1) {
+            moveDataTaskNextRun.add(Calendar.DAY_OF_MONTH, 1);
+        }
+        moveDataTaskNextRun.set(Calendar.HOUR_OF_DAY, 1);
+        moveDataTaskNextRun.set(Calendar.MINUTE, 0);
+        moveDataTaskNextRun.set(Calendar.SECOND, 0);
+        moveDataTaskNextRun.set(Calendar.MILLISECOND, 0);
+    }
+
+    /**
+     * Setter for the primary dataStore
+     * 
+     * @param dataStore
+     */
+    public void setPrimaryDataStore(DataStore dataStore) {
+        this.primaryDataStore = dataStore;
+    }
+
+    /**
+     * Setter for the archive dataStore
+     * 
+     * @param dataStore
+     */
+    public void setArchiveDataStore(DataStore dataStore) {
+        this.archiveDataStore = dataStore;
+    }
+
+    /**
+     * Check if a record for the given identifier exists in the primary data
+     * store. If not found there it will be returned from the archive data
+     * store. If no record exists, this method returns null.
+     * 
+     * @param identifier
+     *            data identifier
+     * @return the record if found, and null if not
+     */
+    public DataRecord getRecordIfStored(DataIdentifier identifier) throws DataStoreException {
+        if (moveDataTaskRunning) {
+            moveDataTaskLock.lock();
+        }
+        try {
+            DataRecord dataRecord = primaryDataStore.getRecordIfStored(identifier);
+            if (dataRecord == null) {
+                dataRecord = archiveDataStore.getRecordIfStored(identifier);
+            }
+            return dataRecord;
+        } finally {
+            if (moveDataTaskRunning) {
+                moveDataTaskLock.unlock();
+            }
+        }
+    }
+
+    /**
+     * Returns the identified data record from the primary data store. If not
+     * found there it will be returned from the archive data store. The given
+     * identifier should be the identifier of a previously saved data record.
+     * Since records are never removed, there should never be cases where the
+     * identified record is not found. Abnormal cases like that are treated as
+     * errors and handled by throwing an exception.
+     * 
+     * @param identifier
+     *            data identifier
+     * @return identified data record
+     * @throws DataStoreException
+     *             if the data store could not be accessed, or if the given
+     *             identifier is invalid
+     */
+    public DataRecord getRecord(DataIdentifier identifier) throws DataStoreException {
+        if (moveDataTaskRunning) {
+            moveDataTaskLock.lock();
+        }
+        try {
+            return primaryDataStore.getRecord(identifier);
+        } catch (DataStoreException e) {
+            return archiveDataStore.getRecord(identifier);
+        } finally {
+            if (moveDataTaskRunning) {
+                moveDataTaskLock.unlock();
+            }
+        }
+    }
+
+    /**
+     * Creates a new data record in the primary data store. The given binary
+     * stream is consumed and a binary record containing the consumed stream is
+     * created and returned. If the same stream already exists in another
+     * record, then that record is returned instead of creating a new one.
+     * <p>
+     * The given stream is consumed and <strong>not closed</strong> by this
+     * method. It is the responsibility of the caller to close the stream. A
+     * typical call pattern would be:
+     * 
+     * <pre>
+     *     InputStream stream = ...;
+     *     try {
+     *         record = store.addRecord(stream);
+     *     } finally {
+     *         stream.close();
+     *     }
+     * </pre>
+     * 
+     * @param stream
+     *            binary stream
+     * @return data record that contains the given stream
+     * @throws DataStoreException
+     *             if the data store could not be accessed
+     */
+    public DataRecord addRecord(InputStream stream) throws DataStoreException {
+        return primaryDataStore.addRecord(stream);
+    }
+
+    /**
+     * From now on, update the modified date of an object even when accessing it
+     * in the archive data store. Usually, the modified date is only updated
+     * when creating a new object, or when a new link is added to an existing
+     * object. When this setting is enabled, even getLength() will update the
+     * modified date.
+     * 
+     * @param before
+     *            - update the modified date to the current time if it is older
+     *            than this value
+     */
+    public void updateModifiedDateOnAccess(long before) {
+        archiveDataStore.updateModifiedDateOnAccess(before);
+    }
+
+    /**
+     * Delete objects that have a modified date older than the specified date
+     * from the archive data store.
+     * 
+     * @param min
+     *            the minimum time
+     * @return the number of data records deleted
+     * @throws DataStoreException
+     */
+    public int deleteAllOlderThan(long min) throws DataStoreException {
+        return archiveDataStore.deleteAllOlderThan(min);
+    }
+
+    /**
+     * Get all identifiers from the archive data store.
+     * 
+     * @return an iterator over all DataIdentifier objects
+     * @throws DataStoreException
+     *             if the list could not be read
+     */
+    public Iterator<DataIdentifier> getAllIdentifiers() throws DataStoreException {
+        return archiveDataStore.getAllIdentifiers();
+    }
+
+    public DataRecord getRecordFromReference(String reference)
+            throws DataStoreException {
+        DataRecord record = primaryDataStore.getRecordFromReference(reference);
+        if (record == null) {
+            record = archiveDataStore.getRecordFromReference(reference);
+        }
+        return record;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void init(String homeDir) throws RepositoryException {
+        if (delayedDelete) {
+            // First initialize the identifiersToDeleteFile
+            LocalFileSystem fileSystem = new LocalFileSystem();
+            fileSystem.setRoot(new File(homeDir));
+            identifiersToDeleteFile = new FileSystemResource(fileSystem, FileSystem.SEPARATOR
+                    + IDENTIFIERS_TO_DELETE_FILE_KEY);
+        }
+        moveDataTaskThread = new Thread(new MoveDataTask(),
+                "Jackrabbit-MulitDataStore-MoveDataTaskThread");
+        moveDataTaskThread.setDaemon(true);
+        moveDataTaskThread.start();
+        log.info("MultiDataStore-MoveDataTask thread started; first run scheduled at "
+                + moveDataTaskNextRun.getTime());
+        if (delayedDelete) {
+            try {
+                // Run on startup the DeleteDelayedIdentifiersTask only if the
+                // file exists and modify date is older than the
+                // delayedDeleteSleep timeout ...
+                if (identifiersToDeleteFile != null
+                        && identifiersToDeleteFile.exists()
+                        && (identifiersToDeleteFile.lastModified() + (delayedDeleteSleep * 1000)) < System
+                                .currentTimeMillis()) {
+                    deleteDelayedIdentifiersTaskThread = new Thread(
+                            //Start immediately ...
+                            new DeleteDelayedIdentifiersTask(0L),
+                            "Jackrabbit-MultiDataStore-DeleteDelayedIdentifiersTaskThread");
+                    deleteDelayedIdentifiersTaskThread.setDaemon(true);
+                    deleteDelayedIdentifiersTaskThread.start();
+                    log.info("Old entries in the " + IDENTIFIERS_TO_DELETE_FILE_KEY
+                            + " File found. DeleteDelayedIdentifiersTask-Thread started now.");
+                }
+            } catch (FileSystemException e) {
+                throw new RepositoryException("I/O error while reading from '"
+                        + identifiersToDeleteFile.getPath() + "'", e);
+            }
+        }
+    }
+
+    /**
+     * Get the minimum size of an object that should be stored in the primary
+     * data store.
+     * 
+     * @return the minimum size in bytes
+     */
+    public int getMinRecordLength() {
+        return primaryDataStore.getMinRecordLength();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void close() throws DataStoreException {
+        DataStoreException lastException = null;
+        // 1. close the primary data store
+        try {
+            primaryDataStore.close();
+        } catch (DataStoreException e) {
+            lastException = e;
+        }
+        // 2. close the archive data store
+        try {
+            archiveDataStore.close();
+        } catch (DataStoreException e) {
+            if (lastException != null) {
+                lastException = new DataStoreException(lastException);
+            }
+        }
+        // 3. if moveDataTaskThread is running interrupt it
+        try {
+            if (moveDataTaskRunning) {
+                moveDataTaskThread.interrupt();
+            }
+        } catch (Exception e) {
+            if (lastException != null) {
+                lastException = new DataStoreException(lastException);
+            }
+        }
+        // 4. if deleteDelayedIdentifiersTaskThread is running interrupt it
+        try {
+            if (deleteDelayedIdentifiersTaskThread != null
+                    && deleteDelayedIdentifiersTaskThread.isAlive()) {
+                deleteDelayedIdentifiersTaskThread.interrupt();
+            }
+        } catch (Exception e) {
+            if (lastException != null) {
+                lastException = new DataStoreException(lastException);
+            }
+        }
+        if (lastException != null) {
+            throw lastException;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void clearInUse() {
+        archiveDataStore.clearInUse();
+    }
+
+    public int getMaxAge() {
+        return maxAge;
+    }
+
+    public void setMaxAge(int maxAge) {
+        this.maxAge = maxAge;
+    }
+
+    public int getMoveDataTaskSleep() {
+        return moveDataTaskSleep;
+    }
+
+    public int getMoveDataTaskFirstRunHourOfDay() {
+        return moveDataTaskNextRun.get(Calendar.HOUR_OF_DAY);
+    }
+
+    public void setMoveDataTaskSleep(int sleep) {
+        this.moveDataTaskSleep = sleep;
+    }
+
+    public void setMoveDataTaskFirstRunHourOfDay(int hourOfDay) {
+        moveDataTaskNextRun = Calendar.getInstance();
+        if (moveDataTaskNextRun.get(Calendar.HOUR_OF_DAY) >= hourOfDay) {
+            moveDataTaskNextRun.add(Calendar.DAY_OF_MONTH, 1);
+        }
+        moveDataTaskNextRun.set(Calendar.HOUR_OF_DAY, hourOfDay);
+        moveDataTaskNextRun.set(Calendar.MINUTE, 0);
+        moveDataTaskNextRun.set(Calendar.SECOND, 0);
+        moveDataTaskNextRun.set(Calendar.MILLISECOND, 0);
+    }
+
+    public void setSleepBetweenRecords(long millis) {
+        this.sleepBetweenRecords = millis;
+    }
+
+    public long getSleepBetweenRecords() {
+        return sleepBetweenRecords;
+    }
+
+    public boolean isDelayedDelete() {
+        return delayedDelete;
+    }
+
+    public void setDelayedDelete(boolean delayedDelete) {
+        this.delayedDelete = delayedDelete;
+    }
+
+    public long getDelayedDeleteSleep() {
+        return delayedDeleteSleep;
+    }
+
+    public void setDelayedDeleteSleep(long delayedDeleteSleep) {
+        this.delayedDeleteSleep = delayedDeleteSleep;
+    }
+
+    /**
+     * Writes the given DataIdentifier to the delayedDeletedFile.
+     * 
+     * @param identifier
+     * @return boolean true if it was successful otherwise false
+     */
+    private boolean writeDelayedDataIdentifier(DataIdentifier identifier) {
+        BufferedWriter writer = null;
+        try {
+            File identifierFile = new File(
+                    ((LocalFileSystem) identifiersToDeleteFile.getFileSystem()).getPath(),
+                    identifiersToDeleteFile.getPath());
+            writer = new BufferedWriter(new FileWriter(identifierFile, true));
+            writer.write(identifier.toString());
+            return true;
+        } catch (Exception e) {
+            log.warn("I/O error while saving DataIdentifier (stacktrace on DEBUG log level) to '"
+                    + identifiersToDeleteFile.getPath() + "': " + e.getMessage());
+            log.debug("Root cause: ", e);
+            return false;
+        } finally {
+            IOUtils.closeQuietly(writer);
+        }
+    }
+
+    /**
+     * Purges the delayedDeletedFile.
+     * 
+     * @return boolean true if it was successful otherwise false
+     */
+    private boolean purgeDelayedDeleteFile() {
+        BufferedWriter writer = null;
+        try {
+            writer = new BufferedWriter(new OutputStreamWriter(
+                    identifiersToDeleteFile.getOutputStream()));
+            writer.write("");
+            return true;
+        } catch (Exception e) {
+            log.warn("I/O error while purging (stacktrace on DEBUG log level) the "
+                    + IDENTIFIERS_TO_DELETE_FILE_KEY + " file '"
+                    + identifiersToDeleteFile.getPath() + "': " + e.getMessage());
+            log.debug("Root cause: ", e);
+            return false;
+        } finally {
+            IOUtils.closeQuietly(writer);
+        }
+    }
+
+    /**
+     * Class for maintaining the MultiDataStore. It will be used to move the
+     * content of the primary data store to the archive data store.
+     */
+    public class MoveDataTask implements Runnable {
+
+        /**
+         * {@inheritDoc}
+         */
+        public void run() {
+            while (!Thread.currentThread().isInterrupted()) {
+                try {
+                    log.info("Next move-data task run scheduled at "
+                            + moveDataTaskNextRun.getTime());
+                    long sleepTime = moveDataTaskNextRun.getTimeInMillis()
+                            - System.currentTimeMillis();
+                    if (sleepTime > 0) {
+                        Thread.sleep(sleepTime);
+                    }
+                    moveDataTaskRunning = true;
+                    moveOutdatedData();
+                    moveDataTaskRunning = false;
+                    moveDataTaskNextRun.add(Calendar.SECOND, moveDataTaskSleep);
+                    if (delayedDelete) {
+                        if (deleteDelayedIdentifiersTaskThread != null
+                                && deleteDelayedIdentifiersTaskThread.isAlive()) {
+                            log.warn("The DeleteDelayedIdentifiersTask-Thread is already running.");
+                        } else {
+                            deleteDelayedIdentifiersTaskThread = new Thread(
+                                    new DeleteDelayedIdentifiersTask(delayedDeleteSleep),
+                                    "Jackrabbit-MultiDataStore-DeleteDelayedIdentifiersTaskThread");
+                            deleteDelayedIdentifiersTaskThread.setDaemon(true);
+                            deleteDelayedIdentifiersTaskThread.start();
+                        }
+                    }
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+            }
+            log.warn("Interrupted: stopping move-data task.");
+        }
+
+        /**
+         * Moves outdated data from primary to archive data store
+         */
+        protected void moveOutdatedData() {
+            try {
+                long now = System.currentTimeMillis();
+                long maxAgeMilli = 1000L * 60 * 60 * 24 * maxAge;
+                log.debug("Collecting all Identifiers from PrimaryDataStore...");
+                Iterator<DataIdentifier> allIdentifiers = primaryDataStore.getAllIdentifiers();
+                int moved = 0;
+                while (allIdentifiers.hasNext()) {
+                    DataIdentifier identifier = allIdentifiers.next();
+                    DataRecord dataRecord = primaryDataStore.getRecord(identifier);
+                    if ((dataRecord.getLastModified() + maxAgeMilli) < now) {
+                        try {
+                            moveDataTaskLock.lock();
+                            if (delayedDelete) {
+                                // first write it to the file and then add it to
+                                // the archive data store ...
+                                if (writeDelayedDataIdentifier(identifier)) {
+                                    archiveDataStore.addRecord(dataRecord.getStream());
+                                    moved++;
+                                }
+                            } else {
+                                // first add it and then delete it .. not really
+                                // atomic ...
+                                archiveDataStore.addRecord(dataRecord.getStream());
+                                ((MultiDataStoreAware) primaryDataStore).deleteRecord(identifier);
+                                moved++;
+                            }
+                            if (moved % 100 == 0) {
+                                log.debug("Moving DataRecord's... ({})", moved);
+                            }
+                        } catch (DataStoreException e) {
+                            log.error("Failed to move DataRecord. DataIdentifier: " + identifier, e);
+                        } finally {
+                            moveDataTaskLock.unlock();
+                        }
+                    }
+                    // Give other threads time to use the MultiDataStore while
+                    // MoveDataTask is running..
+                    Thread.sleep(sleepBetweenRecords);
+                }
+                if (delayedDelete) {
+                    log.info("Moved "
+                            + moved
+                            + " DataRecords to the archive data store. The DataRecords in the primary data store will be removed in "
+                            + delayedDeleteSleep + " seconds.");
+                } else {
+                    log.info("Moved " + moved + " DataRecords to the archive data store.");
+                }
+            } catch (Exception e) {
+                log.warn("Failed to run move-data task.", e);
+            }
+        }
+    }
+
+    /**
+     * Class to clean up the delayed DataRecords from the primary data store.
+     */
+    public class DeleteDelayedIdentifiersTask implements Runnable {
+
+        boolean run = true;
+        private long sleepTime = 0L;
+        
+        /**
+         * Constructor
+         * @param sleep how long this DeleteDelayedIdentifiersTask should sleep in seconds.
+         */
+        public DeleteDelayedIdentifiersTask(long sleep) {
+            this.sleepTime = (sleep * 1000L);
+        }
+
+        @Override
+        public void run() {
+            if (moveDataTaskRunning) {
+                log.warn("It's not supported to run the DeleteDelayedIdentifiersTask while the MoveDataTask is running.");
+                return;
+            }
+            while (run && !Thread.currentThread().isInterrupted()) {
+                if (sleepTime > 0) {
+                    try {
+                        Thread.sleep(sleepTime);
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                    }
+                }
+                log.info("Start to delete DataRecords from the primary data store.");
+                BufferedReader reader = null;
+                ArrayList<DataIdentifier> problemIdentifiers = new ArrayList<DataIdentifier>();
+                try {
+                    int deleted = 0;
+                    reader = new BufferedReader(new InputStreamReader(
+                            identifiersToDeleteFile.getInputStream()));
+                    while (true) {
+                        String s = reader.readLine();
+                        if (s == null || s.equals("")) {
+                            break;
+                        }
+                        DataIdentifier identifier = new DataIdentifier(s);
+                        try {
+                            moveDataTaskLock.lock();
+                            ((MultiDataStoreAware) primaryDataStore).deleteRecord(identifier);
+                            deleted++;
+                        } catch (DataStoreException e) {
+                            log.error("Failed to delete DataRecord. DataIdentifier: " + identifier,
+                                    e);
+                            problemIdentifiers.add(identifier);
+                        } finally {
+                            moveDataTaskLock.unlock();
+                        }
+                        // Give other threads time to use the MultiDataStore
+                        // while
+                        // DeleteDelayedIdentifiersTask is running..
+                        Thread.sleep(sleepBetweenRecords);
+                    }
+                    log.info("Deleted " + deleted + " DataRecords from the primary data store.");
+                    if (problemIdentifiers.isEmpty()) {
+                        try {
+                            identifiersToDeleteFile.delete();
+                        } catch (FileSystemException e) {
+                            log.warn("Unable to delete the " + IDENTIFIERS_TO_DELETE_FILE_KEY
+                                    + " File.");
+                            if (!purgeDelayedDeleteFile()) {
+                                log.error("Unable to purge the " + IDENTIFIERS_TO_DELETE_FILE_KEY
+                                        + " File.");
+                            }
+                        }
+                    } else {
+                        if (purgeDelayedDeleteFile()) {
+                            for (int x = 0; x < problemIdentifiers.size(); x++) {
+                                writeDelayedDataIdentifier(problemIdentifiers.get(x));
+                            }
+                        }
+                    }
+                } catch (InterruptedException e) {
+                    log.warn("Interrupted: stopping delayed-delete task.");
+                    Thread.currentThread().interrupt();
+                } catch (Exception e) {
+                    log.warn("Failed to run delayed-delete task.", e);
+                } finally {
+                    IOUtils.closeQuietly(reader);
+                    run = false;
+                }
+            }
+        }
+    }
+
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/MultiDataStoreAware.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/MultiDataStoreAware.java
new file mode 100644
index 0000000..df526c2
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/MultiDataStoreAware.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+/**
+ * To use a DataStore within a MultiDataStore it must implement this
+ * MultiDataStoreAware Interface. It extends a DataStore to delete a single
+ * DataRecord.
+ */
+public interface MultiDataStoreAware {
+
+    /**
+     * Deletes a single DataRecord based on the given identifier. Delete will
+     * only be used by the {@link MoveDataTask}.
+     * 
+     * @param identifier
+     *            data identifier
+     * @throws DataStoreException
+     *             if the data store could not be accessed, or if the given
+     *             identifier is invalid
+     */
+    void deleteRecord(DataIdentifier identifier) throws DataStoreException;
+
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/ScanEventListener.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/ScanEventListener.java
similarity index 100%
rename from jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/ScanEventListener.java
rename to jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/ScanEventListener.java
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/db/DbDataRecord.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/db/DbDataRecord.java
new file mode 100644
index 0000000..417b3ce
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/db/DbDataRecord.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data.db;
+
+import org.apache.jackrabbit.core.data.AbstractDataRecord;
+import org.apache.jackrabbit.core.data.DataIdentifier;
+import org.apache.jackrabbit.core.data.DataStoreException;
+
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+
+/**
+ * Data record that is stored in a database
+ */
+public class DbDataRecord extends AbstractDataRecord {
+
+    protected final DbDataStore store;
+    protected final long length;
+    protected long lastModified;
+
+    /**
+     * Creates a data record based on the given identifier and length.
+     *
+     * @param identifier data identifier
+     * @param length the length
+     * @param lastModified
+     */
+    public DbDataRecord(DbDataStore store, DataIdentifier identifier, long length, long lastModified) {
+        super(store, identifier);
+        this.store = store;
+        this.length = length;
+        this.lastModified = lastModified;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getLength() throws DataStoreException {
+        lastModified = store.touch(getIdentifier(), lastModified);
+        return length;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public InputStream getStream() throws DataStoreException {
+        lastModified = store.touch(getIdentifier(), lastModified);
+        return new BufferedInputStream(new DbInputStream(store, getIdentifier()));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getLastModified() {
+        return lastModified;
+    }
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java
new file mode 100644
index 0000000..63a90b0
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/db/DbDataStore.java
@@ -0,0 +1,1013 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data.db;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.input.CountingInputStream;
+import org.apache.jackrabbit.core.data.AbstractDataStore;
+import org.apache.jackrabbit.core.data.DataIdentifier;
+import org.apache.jackrabbit.core.data.DataRecord;
+import org.apache.jackrabbit.core.data.DataStoreException;
+import org.apache.jackrabbit.core.data.MultiDataStoreAware;
+import org.apache.jackrabbit.core.util.db.CheckSchemaOperation;
+import org.apache.jackrabbit.core.util.db.ConnectionFactory;
+import org.apache.jackrabbit.core.util.db.ConnectionHelper;
+import org.apache.jackrabbit.core.util.db.DatabaseAware;
+import org.apache.jackrabbit.core.util.db.DbUtility;
+import org.apache.jackrabbit.core.util.db.StreamWrapper;
+import org.apache.jackrabbit.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.WeakReference;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.UUID;
+import java.util.WeakHashMap;
+
+import javax.jcr.RepositoryException;
+import javax.sql.DataSource;
+
+/**
+ * A data store implementation that stores the records in a database using JDBC.
+ *
+ * Configuration:
+ * <pre>
+ * <DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
+ *     <param name="{@link #setUrl(String) url}" value="jdbc:postgresql:test"/>
+ *     <param name="{@link #setUser(String) user}" value="sa"/>
+ *     <param name="{@link #setPassword(String) password}" value="sa"/>
+ *     <param name="{@link #setDatabaseType(String) databaseType}" value="postgresql"/>
+ *     <param name="{@link #setDriver(String) driver}" value="org.postgresql.Driver"/>
+ *     <param name="{@link #setMinRecordLength(int) minRecordLength}" value="1024"/>
+ *     <param name="{@link #setMaxConnections(int) maxConnections}" value="2"/>
+ *     <param name="{@link #setCopyWhenReading(boolean) copyWhenReading}" value="true"/>
+ *     <param name="{@link #setTablePrefix(String) tablePrefix}" value=""/>
+ *     <param name="{@link #setSchemaObjectPrefix(String) schemaObjectPrefix}" value=""/>
+ *     <param name="{@link #setSchemaCheckEnabled(String) schemaCheckEnabled}" value="true"/>
+ * &lt/DataStore>
+ * </pre>
+ * <p>
+ * Only URL, user name and password usually need to be set.
+ * The remaining settings are generated using the database URL sub-protocol from the
+ * database type resource file.
+ * <p>
+ * JNDI can be used to get the connection. In this case, use the javax.naming.InitialContext as the driver,
+ * and the JNDI name as the URL. If the user and password are configured in the JNDI resource,
+ * they should not be configured here. Example JNDI settings:
+ * <pre>
+ * <param name="driver" value="javax.naming.InitialContext" />
+ * <param name="url" value="java:comp/env/jdbc/Test" />
+ * </pre>
+ * <p>
+ * For Microsoft SQL Server 2005, there is a problem reading large BLOBs. You will need to use
+ * the JDBC driver version 1.2 or newer, and append ;responseBuffering=adaptive to the database URL.
+ * Don't append ;selectMethod=cursor, otherwise it can still run out of memory.
+ * Example database URL: jdbc:sqlserver://localhost:4220;DatabaseName=test;responseBuffering=adaptive
+ * <p>
+ * By default, the data is copied to a temp file when reading, to avoid problems when reading multiple
+ * blobs at the same time.
+ * <p>
+ * The tablePrefix can be used to specify a schema and / or catalog name:
+ * <param name="tablePrefix" value="ds.">
+ */
+public class DbDataStore extends AbstractDataStore
+        implements DatabaseAware, MultiDataStoreAware {
+
+    /**
+     * The default value for the minimum object size.
+     */
+    public static final int DEFAULT_MIN_RECORD_LENGTH = 100;
+
+    /**
+     * Write to a temporary file to get the length (slow, but always works).
+     * This is the default setting.
+     */
+    public static final String STORE_TEMP_FILE = "tempFile";
+
+    /**
+     * Call PreparedStatement.setBinaryStream(..., -1)
+     */
+    public static final String STORE_SIZE_MINUS_ONE = "-1";
+
+    /**
+     * Call PreparedStatement.setBinaryStream(..., Integer.MAX_VALUE)
+     */
+    public static final String STORE_SIZE_MAX = "max";
+
+    /**
+     * The digest algorithm used to uniquely identify records.
+     */
+    protected static final String DIGEST = "SHA-1";
+
+    /**
+     * The prefix used for temporary objects.
+     */
+    protected static final String TEMP_PREFIX = "TEMP_";
+
+    /**
+     * Logger instance
+     */
+    private static Logger log = LoggerFactory.getLogger(DbDataStore.class);
+
+    /**
+     * The minimum modified date. If a file is accessed (read or write) with a modified date
+     * older than this value, the modified date is updated to the current time.
+     */
+    protected long minModifiedDate;
+
+    /**
+     * The database URL used.
+     */
+    protected String url;
+
+    /**
+     * The database driver.
+     */
+    protected String driver;
+
+    /**
+     * The user name.
+     */
+    protected String user;
+
+    /**
+     * The password
+     */
+    protected String password;
+
+    /**
+     * The database type used.
+     */
+    protected String databaseType;
+
+    /**
+     * The minimum size of an object that should be stored in this data store.
+     */
+    protected int minRecordLength = DEFAULT_MIN_RECORD_LENGTH;
+
+    /**
+     * The prefix for the datastore table, empty by default.
+     */
+    protected String tablePrefix = "";
+
+    /**
+     * The prefix of the table names. By default it is empty.
+     */
+    protected String schemaObjectPrefix = "";
+
+    /**
+     * Whether the schema check must be done during initialization.
+     */
+    private boolean schemaCheckEnabled = true;
+
+    /**
+     * The logical name of the DataSource to use.
+     */
+    protected String dataSourceName;
+
+    /**
+     * This is the property 'table'
+     * in the [databaseType].properties file, initialized with the default value.
+     */
+    protected String tableSQL = "DATASTORE";
+
+    /**
+     * This is the property 'createTable'
+     * in the [databaseType].properties file, initialized with the default value.
+     */
+    protected String createTableSQL =
+        "CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY, LENGTH BIGINT, LAST_MODIFIED BIGINT, DATA BLOB)";
+
+    /**
+     * This is the property 'insertTemp'
+     * in the [databaseType].properties file, initialized with the default value.
+     */
+    protected String insertTempSQL =
+        "INSERT INTO ${tablePrefix}${table} VALUES(?, 0, ?, NULL)";
+
+    /**
+     * This is the property 'updateData'
+     * in the [databaseType].properties file, initialized with the default value.
+     */
+    protected String updateDataSQL =
+        "UPDATE ${tablePrefix}${table} SET DATA=? WHERE ID=?";
+
+    /**
+     * This is the property 'updateLastModified'
+     * in the [databaseType].properties file, initialized with the default value.
+     */
+    protected String updateLastModifiedSQL =
+        "UPDATE ${tablePrefix}${table} SET LAST_MODIFIED=? WHERE ID=? AND LAST_MODIFIED<?";
+
+    /**
+     * This is the property 'update'
+     * in the [databaseType].properties file, initialized with the default value.
+     */
+    protected String updateSQL =
+        "UPDATE ${tablePrefix}${table} SET ID=?, LENGTH=?, LAST_MODIFIED=? " +
+        "WHERE ID=? AND LAST_MODIFIED=?";
+
+    /**
+     * This is the property 'delete'
+     * in the [databaseType].properties file, initialized with the default value.
+     */
+    protected String deleteSQL =
+        "DELETE FROM ${tablePrefix}${table} WHERE ID=?";
+
+    /**
+     * This is the property 'deleteOlder'
+     * in the [databaseType].properties file, initialized with the default value.
+     */
+    protected String deleteOlderSQL =
+        "DELETE FROM ${tablePrefix}${table} WHERE LAST_MODIFIED<?";
+
+    /**
+     * This is the property 'selectMeta'
+     * in the [databaseType].properties file, initialized with the default value.
+     */
+    protected String selectMetaSQL =
+        "SELECT LENGTH, LAST_MODIFIED FROM ${tablePrefix}${table} WHERE ID=?";
+
+    /**
+     * This is the property 'selectAll'
+     * in the [databaseType].properties file, initialized with the default value.
+     */
+    protected String selectAllSQL =
+        "SELECT ID FROM ${tablePrefix}${table}";
+
+    /**
+     * This is the property 'selectData'
+     * in the [databaseType].properties file, initialized with the default value.
+     */
+    protected String selectDataSQL =
+        "SELECT ID, DATA FROM ${tablePrefix}${table} WHERE ID=?";
+
+    /**
+     * The stream storing mechanism used.
+     */
+    protected String storeStream = STORE_TEMP_FILE;
+
+    /**
+     * Copy the stream to a temp file before returning it.
+     * Enabled by default to support concurrent reads.
+     */
+    protected boolean copyWhenReading = true;
+
+    /**
+     * All data identifiers that are currently in use are in this set until they are garbage collected.
+     */
+    protected Map<DataIdentifier, WeakReference<DataIdentifier>> inUse =
+        Collections.synchronizedMap(new WeakHashMap<DataIdentifier, WeakReference<DataIdentifier>>());
+
+    /**
+     * The temporary identifiers that are currently in use.
+     */
+    protected List<String> temporaryInUse = Collections.synchronizedList(new ArrayList<String>());
+
+    /**
+     * The {@link ConnectionHelper} set in the {@link #init(String)} method.
+     * */
+    protected ConnectionHelper conHelper;
+
+    /**
+     * The repositories {@link ConnectionFactory}.
+     */
+    private ConnectionFactory connectionFactory;
+
+    public void setConnectionFactory(ConnectionFactory connnectionFactory) {
+        this.connectionFactory = connnectionFactory;
+    }
+
+    public DataRecord addRecord(InputStream stream) throws DataStoreException {
+        InputStream fileInput = null;
+        String tempId = null;
+        ResultSet rs = null;
+        try {
+            long tempModified;
+            while (true) {
+                try {
+                    tempModified = System.currentTimeMillis();
+                    String id = UUID.randomUUID().toString();
+                    tempId = TEMP_PREFIX + id;
+                    temporaryInUse.add(tempId);
+                    // SELECT LENGTH, LAST_MODIFIED FROM DATASTORE WHERE ID=?
+                    rs = conHelper.query(selectMetaSQL, tempId);
+                    boolean hasNext = rs.next();
+                    DbUtility.close(rs);
+                    rs = null;
+                    if (hasNext) {
+                        // re-try in the very, very unlikely event that the row already exists
+                        continue;
+                    }
+                    // INSERT INTO DATASTORE VALUES(?, 0, ?, NULL)
+                    conHelper.exec(insertTempSQL, tempId, tempModified);
+                    break;
+                } catch (Exception e) {
+                    throw convert("Can not insert new record", e);
+                } finally {
+                    DbUtility.close(rs);
+                    // prevent that rs.close() is called again
+                    rs = null;
+                }
+            }
+            MessageDigest digest = getDigest();
+            DigestInputStream dIn = new DigestInputStream(stream, digest);
+            CountingInputStream in = new CountingInputStream(dIn);
+            StreamWrapper wrapper;
+            if (STORE_SIZE_MINUS_ONE.equals(storeStream)) {
+                wrapper = new StreamWrapper(in, -1);
+            } else if (STORE_SIZE_MAX.equals(storeStream)) {
+                wrapper = new StreamWrapper(in, Integer.MAX_VALUE);
+            } else if (STORE_TEMP_FILE.equals(storeStream)) {
+                File temp = moveToTempFile(in);
+                long length = temp.length();
+                wrapper = new StreamWrapper(new ResettableTempFileInputStream(temp), length);
+            } else {
+                throw new DataStoreException("Unsupported stream store algorithm: " + storeStream);
+            }
+            // UPDATE DATASTORE SET DATA=? WHERE ID=?
+            conHelper.exec(updateDataSQL, wrapper, tempId);
+            long length = in.getByteCount();
+            DataIdentifier identifier =
+                    new DataIdentifier(encodeHexString(digest.digest()));
+            usesIdentifier(identifier);
+            String id = identifier.toString();
+            long newModified;
+            while (true) {
+                newModified = System.currentTimeMillis();
+                if (checkExisting(tempId, length, identifier)) {
+                    touch(identifier, newModified);
+                    conHelper.exec(deleteSQL, tempId);
+                    break;
+                }
+                try {
+                    // UPDATE DATASTORE SET ID=?, LENGTH=?, LAST_MODIFIED=?
+                    // WHERE ID=? AND LAST_MODIFIED=?
+                    int count = conHelper.update(updateSQL,
+                            id, length, newModified, tempId, tempModified);
+                    // If update count is 0, the last modified time of the
+                    // temporary row was changed - which means we need to
+                    // re-try using a new last modified date (a later one)
+                    // because we need to ensure the new last modified date
+                    // is _newer_ than the old (otherwise the garbage
+                    // collection could delete rows)
+                    if (count != 0) {
+                        // update was successful
+                        break;
+                    }
+                } catch (SQLException e) {
+                    // duplicate key (the row already exists) - repeat
+                    // we use exception handling for flow control here, which is bad,
+                    // but the alternative is to use UPDATE ... WHERE ... (SELECT ...)
+                    // which could cause a deadlock in some databases - also,
+                    // duplicate key will only occur if somebody else concurrently
+                    // added the same record (which is very unlikely)
+                }
+                // SELECT LENGTH, LAST_MODIFIED FROM DATASTORE WHERE ID=?
+                rs = conHelper.query(selectMetaSQL, tempId);
+                if (!rs.next()) {
+                    // the row was deleted, which is unexpected / not allowed
+                    String msg =
+                        DIGEST + " temporary entry deleted: " +
+                            " id=" + tempId + " length=" + length;
+                    log.error(msg);
+                    throw new DataStoreException(msg);
+                }
+                tempModified = rs.getLong(2);
+                DbUtility.close(rs);
+                rs = null;
+            }
+            usesIdentifier(identifier);
+            DbDataRecord record = new DbDataRecord(this, identifier, length, newModified);
+            return record;
+        } catch (Exception e) {
+            throw convert("Can not insert new record", e);
+        } finally {
+            if (tempId != null) {
+                temporaryInUse.remove(tempId);
+            }
+            DbUtility.close(rs);
+            if (fileInput != null) {
+                try {
+                    fileInput.close();
+                } catch (IOException e) {
+                    throw convert("Can not close temporary file", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Check if a row with this ID already exists.
+     *
+     * @return true if the row exists and the length matches
+     * @throw DataStoreException if a row exists, but the length is different
+     */
+    private boolean checkExisting(String tempId, long length, DataIdentifier identifier) throws DataStoreException, SQLException {
+        String id = identifier.toString();
+        // SELECT LENGTH, LAST_MODIFIED FROM DATASTORE WHERE ID=?
+        ResultSet rs = null;
+        try {
+            rs = conHelper.query(selectMetaSQL, id);
+            if (rs.next()) {
+                long oldLength = rs.getLong(1);
+                long lastModified = rs.getLong(2);
+                if (oldLength != length) {
+                    String msg =
+                        DIGEST + " collision: temp=" + tempId
+                        + " id=" + id + " length=" + length
+                        + " oldLength=" + oldLength;
+                    log.error(msg);
+                    throw new DataStoreException(msg);
+                }
+                DbUtility.close(rs);
+                rs = null;
+                touch(identifier, lastModified);
+                // row already exists
+                conHelper.exec(deleteSQL, tempId);
+                return true;
+            }
+        } finally {
+            DbUtility.close(rs);
+        }
+        return false;
+    }
+
+    /**
+     * Creates a temp file and copies the data there.
+     * The input stream is closed afterwards.
+     *
+     * @param in the input stream
+     * @return the file
+     * @throws IOException
+     */
+    private File moveToTempFile(InputStream in) throws IOException {
+        File temp = File.createTempFile("dbRecord", null);
+        writeToFileAndClose(in, temp);
+        return temp;
+    }
+
+    private void writeToFileAndClose(InputStream in, File file) throws IOException {
+        OutputStream out = new FileOutputStream(file);
+        try {
+            IOUtils.copy(in, out);
+        } finally {
+            IOUtils.closeQuietly(out);
+            IOUtils.closeQuietly(in);
+        }
+    }
+
+    public synchronized void deleteRecord(DataIdentifier identifier) throws DataStoreException {
+        try {
+            conHelper.exec(deleteSQL, identifier.toString());
+        } catch (Exception e) {
+            throw convert("Can not delete record", e);
+        }
+    }
+
+    public synchronized int deleteAllOlderThan(long min) throws DataStoreException {
+        try {
+            ArrayList<String> touch = new ArrayList<String>();
+            ArrayList<DataIdentifier> ids = new ArrayList<DataIdentifier>(inUse.keySet());
+            for (DataIdentifier identifier: ids) {
+                if (identifier != null) {
+                    touch.add(identifier.toString());
+                }
+            }
+            touch.addAll(temporaryInUse);
+            for (String key : touch) {
+                updateLastModifiedDate(key, 0);
+            }
+            // DELETE FROM DATASTORE WHERE LAST_MODIFIED<?
+            return conHelper.update(deleteOlderSQL, min);
+        } catch (Exception e) {
+            throw convert("Can not delete records", e);
+        }
+    }
+
+    public Iterator<DataIdentifier> getAllIdentifiers() throws DataStoreException {
+        ArrayList<DataIdentifier> list = new ArrayList<DataIdentifier>();
+        ResultSet rs = null;
+        try {
+            // SELECT ID FROM DATASTORE
+            rs = conHelper.query(selectAllSQL);
+            while (rs.next()) {
+                String id = rs.getString(1);
+                if (!id.startsWith(TEMP_PREFIX)) {
+                    DataIdentifier identifier = new DataIdentifier(id);
+                    list.add(identifier);
+                }
+            }
+            log.debug("Found " + list.size() + " identifiers.");
+            return list.iterator();
+        } catch (Exception e) {
+            throw convert("Can not read records", e);
+        } finally {
+            DbUtility.close(rs);
+        }
+    }
+
+    public int getMinRecordLength() {
+        return minRecordLength;
+    }
+
+    /**
+     * Set the minimum object length.
+     * The maximum value is around 32000.
+     *
+     * @param minRecordLength the length
+     */
+    public void setMinRecordLength(int minRecordLength) {
+        this.minRecordLength = minRecordLength;
+    }
+
+    public DataRecord getRecordIfStored(DataIdentifier identifier) throws DataStoreException {
+        usesIdentifier(identifier);
+        ResultSet rs = null;
+        try {
+            String id = identifier.toString();
+            // SELECT LENGTH, LAST_MODIFIED FROM DATASTORE WHERE ID = ?
+            rs = conHelper.query(selectMetaSQL, id);
+            if (!rs.next()) {
+                return null;
+            }
+            long length = rs.getLong(1);
+            long lastModified = rs.getLong(2);
+            DbUtility.close(rs);
+            rs = null;
+            lastModified = touch(identifier, lastModified);
+            return new DbDataRecord(this, identifier, length, lastModified);
+        } catch (Exception e) {
+            throw convert("Can not read identifier " + identifier, e);
+        } finally {
+            DbUtility.close(rs);
+        }
+    }
+
+    /**
+     * Open the input stream. This method sets those fields of the caller
+     * that need to be closed once the input stream is read.
+     *
+     * @param inputStream the database input stream object
+     * @param identifier data identifier
+     * @throws DataStoreException if the data store could not be accessed,
+     *          or if the given identifier is invalid
+     */
+    InputStream openStream(DbInputStream inputStream, DataIdentifier identifier) throws DataStoreException {
+        ResultSet rs = null;
+        try {
+            // SELECT ID, DATA FROM DATASTORE WHERE ID = ?
+            rs = conHelper.query(selectDataSQL, identifier.toString());
+            if (!rs.next()) {
+                throw new DataStoreException("Record not found: " + identifier);
+            }
+            InputStream stream = rs.getBinaryStream(2);
+            if (stream == null) {
+                stream = new ByteArrayInputStream(new byte[0]);
+                DbUtility.close(rs);
+            } else if (copyWhenReading) {
+                // If we copy while reading, create a temp file and close the stream
+                File temp = moveToTempFile(stream);
+                stream = new BufferedInputStream(new TempFileInputStream(temp));
+                DbUtility.close(rs);
+            } else {
+                stream = new BufferedInputStream(stream);
+                inputStream.setResultSet(rs);
+            }
+            return stream;
+        } catch (Exception e) {
+            DbUtility.close(rs);
+            throw convert("Retrieving database resource ", e);
+        }
+    }
+
+    public synchronized void init(String homeDir) throws DataStoreException {
+        try {
+            initDatabaseType();
+
+            conHelper = createConnectionHelper(getDataSource());
+
+            if (isSchemaCheckEnabled()) {
+                createCheckSchemaOperation().run();
+            }
+        } catch (Exception e) {
+            throw convert("Can not init data store, driver=" + driver + " url=" + url + " user=" + user +
+                    " schemaObjectPrefix=" + schemaObjectPrefix + " tableSQL=" + tableSQL + " createTableSQL=" + createTableSQL, e);
+        }
+    }
+
+    private DataSource getDataSource() throws Exception {
+        if (getDataSourceName() == null || "".equals(getDataSourceName())) {
+            return connectionFactory.getDataSource(getDriver(), getUrl(), getUser(), getPassword());
+        } else {
+            return connectionFactory.getDataSource(dataSourceName);
+        }
+    }
+
+    /**
+     * This method is called from the {@link #init(String)} method of this class and returns a
+     * {@link ConnectionHelper} instance which is assigned to the {@code conHelper} field. Subclasses may
+     * override it to return a specialized connection helper.
+     *
+     * @param dataSrc the {@link DataSource} of this persistence manager
+     * @return a {@link ConnectionHelper}
+     * @throws Exception on error
+     */
+    protected ConnectionHelper createConnectionHelper(DataSource dataSrc) throws Exception {
+        return new ConnectionHelper(dataSrc, false);
+    }
+
+    /**
+     * This method is called from {@link #init(String)} after the
+     * {@link #createConnectionHelper(DataSource)} method, and returns a default {@link CheckSchemaOperation}.
+     *
+     * @return a new {@link CheckSchemaOperation} instance
+     */
+    protected final CheckSchemaOperation createCheckSchemaOperation() {
+        String tableName = tablePrefix + schemaObjectPrefix + tableSQL;
+        return new CheckSchemaOperation(conHelper, new ByteArrayInputStream(createTableSQL.getBytes()), tableName);
+    }
+
+    protected void initDatabaseType() throws DataStoreException {
+        boolean failIfNotFound = false;
+        if (databaseType == null) {
+            if (dataSourceName != null) {
+                try {
+                    databaseType = connectionFactory.getDataBaseType(dataSourceName);
+                } catch (RepositoryException e) {
+                    throw new DataStoreException(e);
+                }
+            } else {
+                if (!url.startsWith("jdbc:")) {
+                    return;
+                }
+                int start = "jdbc:".length();
+                int end = url.indexOf(':', start);
+                databaseType = url.substring(start, end);
+            }
+        } else {
+            failIfNotFound = true;
+        }
+
+        InputStream in =
+            DbDataStore.class.getResourceAsStream(databaseType + ".properties");
+        if (in == null) {
+            if (failIfNotFound) {
+                String msg =
+                    "Configuration error: The resource '" + databaseType
+                    + ".properties' could not be found;"
+                    + " Please verify the databaseType property";
+                log.debug(msg);
+                throw new DataStoreException(msg);
+            } else {
+                return;
+            }
+        }
+        Properties prop = new Properties();
+        try {
+            try {
+                prop.load(in);
+            } finally {
+            in.close();
+            }
+        } catch (IOException e) {
+            String msg = "Configuration error: Could not read properties '" + databaseType + ".properties'";
+            log.debug(msg);
+            throw new DataStoreException(msg, e);
+        }
+        if (driver == null) {
+            driver = getProperty(prop, "driver", driver);
+        }
+        tableSQL = getProperty(prop, "table", tableSQL);
+        createTableSQL = getProperty(prop, "createTable", createTableSQL);
+        insertTempSQL = getProperty(prop, "insertTemp", insertTempSQL);
+        updateDataSQL = getProperty(prop, "updateData", updateDataSQL);
+        updateLastModifiedSQL = getProperty(prop, "updateLastModified", updateLastModifiedSQL);
+        updateSQL = getProperty(prop, "update", updateSQL);
+        deleteSQL = getProperty(prop, "delete", deleteSQL);
+        deleteOlderSQL = getProperty(prop, "deleteOlder", deleteOlderSQL);
+        selectMetaSQL = getProperty(prop, "selectMeta", selectMetaSQL);
+        selectAllSQL = getProperty(prop, "selectAll", selectAllSQL);
+        selectDataSQL = getProperty(prop, "selectData", selectDataSQL);
+        storeStream = getProperty(prop, "storeStream", storeStream);
+        if (!STORE_SIZE_MINUS_ONE.equals(storeStream)
+                && !STORE_TEMP_FILE.equals(storeStream)
+                && !STORE_SIZE_MAX.equals(storeStream)) {
+            String msg = "Unsupported Stream store mechanism: " + storeStream
+                    + " supported are: " + STORE_SIZE_MINUS_ONE + ", "
+                    + STORE_TEMP_FILE + ", " + STORE_SIZE_MAX;
+            log.debug(msg);
+            throw new DataStoreException(msg);
+        }
+    }
+
+    /**
+     * Get the expanded property value. The following placeholders are supported:
+     * ${table}: the table name (the default is DATASTORE) and
+     * ${tablePrefix}: tablePrefix plus schemaObjectPrefix as set in the configuration
+     *
+     * @param prop the properties object
+     * @param key the key
+     * @param defaultValue the default value
+     * @return the property value (placeholders are replaced)
+     */
+    protected String getProperty(Properties prop, String key, String defaultValue) {
+        String sql = prop.getProperty(key, defaultValue);
+        sql = Text.replace(sql, "${table}", tableSQL).trim();
+        sql = Text.replace(sql, "${tablePrefix}", tablePrefix + schemaObjectPrefix).trim();
+        return sql;
+    }
+
+    /**
+     * Convert an exception to a data store exception.
+     *
+     * @param cause the message
+     * @param e the root cause
+     * @return the data store exception
+     */
+    protected DataStoreException convert(String cause, Exception e) {
+        log.warn(cause, e);
+        if (e instanceof DataStoreException) {
+            return (DataStoreException) e;
+        } else {
+            return new DataStoreException(cause, e);
+        }
+    }
+
+    public void updateModifiedDateOnAccess(long before) {
+        log.debug("Update modifiedDate on access before " + before);
+        minModifiedDate = before;
+    }
+
+    /**
+     * Update the modified date of an entry if required.
+     *
+     * @param identifier the entry identifier
+     * @param lastModified the current last modified date
+     * @return the new modified date
+     */
+    long touch(DataIdentifier identifier, long lastModified) throws DataStoreException {
+        usesIdentifier(identifier);
+        return updateLastModifiedDate(identifier.toString(), lastModified);
+    }
+
+    private long updateLastModifiedDate(String key, long lastModified) throws DataStoreException {
+        if (lastModified < minModifiedDate) {
+            long now = System.currentTimeMillis();
+            try {
+                // UPDATE DATASTORE SET LAST_MODIFIED = ? WHERE ID = ? AND LAST_MODIFIED < ?
+                conHelper.update(updateLastModifiedSQL, now, key, now);
+                return now;
+            } catch (Exception e) {
+                throw convert("Can not update lastModified", e);
+            }
+        }
+        return lastModified;
+    }
+
+    /**
+     * Get the database type (if set).
+     * @return the database type
+     */
+    public String getDatabaseType() {
+        return databaseType;
+    }
+
+    /**
+     * Set the database type. By default the sub-protocol of the JDBC database URL is used if it is not set.
+     * It must match the resource file [databaseType].properties. Example: mysql.
+     *
+     * @param databaseType
+     */
+    public void setDatabaseType(String databaseType) {
+        this.databaseType = databaseType;
+    }
+
+    /**
+     * Get the database driver
+     *
+     * @return the driver
+     */
+    public String getDriver() {
+        return driver;
+    }
+
+    /**
+     * Set the database driver class name.
+     * If not set, the default driver class name for the database type is used,
+     * as set in the [databaseType].properties resource; key 'driver'.
+     *
+     * @param driver
+     */
+    public void setDriver(String driver) {
+        this.driver = driver;
+    }
+
+    /**
+     * Get the password.
+     *
+     * @return the password
+     */
+    public String getPassword() {
+        return password;
+    }
+
+    /**
+     * Set the password.
+     *
+     * @param password
+     */
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    /**
+     * Get the database URL.
+     *
+     * @return the URL
+     */
+    public String getUrl() {
+        return url;
+    }
+
+    /**
+     * Set the database URL.
+     * Example: jdbc:postgresql:test
+     *
+     * @param url
+     */
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    /**
+     * Get the user name.
+     *
+     * @return the user name
+     */
+    public String getUser() {
+        return user;
+    }
+
+    /**
+     * Set the user name.
+     *
+     * @param user
+     */
+    public void setUser(String user) {
+        this.user = user;
+    }
+
+    /**
+     * @return whether the schema check is enabled
+     */
+    public final boolean isSchemaCheckEnabled() {
+        return schemaCheckEnabled;
+    }
+
+    /**
+     * @param enabled set whether the schema check is enabled
+     */
+    public final void setSchemaCheckEnabled(boolean enabled) {
+        schemaCheckEnabled = enabled;
+    }
+
+    public synchronized void close() throws DataStoreException {
+        // nothing to do
+    }
+
+    protected void usesIdentifier(DataIdentifier identifier) {
+        inUse.put(identifier, new WeakReference<DataIdentifier>(identifier));
+    }
+
+    public void clearInUse() {
+        inUse.clear();
+    }
+
+    protected synchronized MessageDigest getDigest() throws DataStoreException {
+        try {
+            return MessageDigest.getInstance(DIGEST);
+        } catch (NoSuchAlgorithmException e) {
+            throw convert("No such algorithm: " + DIGEST, e);
+        }
+    }
+
+    /**
+     * Get the maximum number of concurrent connections.
+     *
+     * @deprecated
+     * @return the maximum number of connections.
+     */
+    public int getMaxConnections() {
+        return -1;
+    }
+
+    /**
+     * Set the maximum number of concurrent connections in the pool.
+     * At least 3 connections are required if the garbage collection process is used.
+     *
+     *@deprecated
+     * @param maxConnections the new value
+     */
+    public void setMaxConnections(int maxConnections) {
+        // no effect
+    }
+
+    /**
+     * Is a stream copied to a temporary file before returning?
+     *
+     * @return the setting
+     */
+    public boolean getCopyWhenReading() {
+        return copyWhenReading;
+    }
+
+    /**
+     * The the copy setting. If enabled,
+     * a stream is always copied to a temporary file when reading a stream.
+     *
+     * @param copyWhenReading the new setting
+     */
+    public void setCopyWhenReading(boolean copyWhenReading) {
+        this.copyWhenReading = copyWhenReading;
+    }
+
+    /**
+     * Get the table prefix.
+     *
+     * @return the table prefix.
+     */
+    public String getTablePrefix() {
+        return tablePrefix;
+    }
+
+    /**
+     * Set the new table prefix. The default is empty.
+     * The table name is constructed like this:
+     * ${tablePrefix}${schemaObjectPrefix}${tableName}
+     *
+     * @param tablePrefix the new value
+     */
+    public void setTablePrefix(String tablePrefix) {
+        this.tablePrefix = tablePrefix;
+    }
+
+    /**
+     * Get the schema prefix.
+     *
+     * @return the schema object prefix
+     */
+    public String getSchemaObjectPrefix() {
+        return schemaObjectPrefix;
+    }
+
+    /**
+     * Set the schema object prefix. The default is empty.
+     * The table name is constructed like this:
+     * ${tablePrefix}${schemaObjectPrefix}${tableName}
+     *
+     * @param schemaObjectPrefix the new prefix
+     */
+    public void setSchemaObjectPrefix(String schemaObjectPrefix) {
+        this.schemaObjectPrefix = schemaObjectPrefix;
+    }
+
+    public String getDataSourceName() {
+        return dataSourceName;
+    }
+
+    public void setDataSourceName(String dataSourceName) {
+        this.dataSourceName = dataSourceName;
+    }
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbInputStream.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/db/DbInputStream.java
similarity index 100%
rename from jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DbInputStream.java
rename to jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/db/DbInputStream.java
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DerbyDataStore.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/db/DerbyDataStore.java
similarity index 100%
rename from jackrabbit-core/src/main/java/org/apache/jackrabbit/core/data/db/DerbyDataStore.java
rename to jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/db/DerbyDataStore.java
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/db/ResettableTempFileInputStream.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/db/ResettableTempFileInputStream.java
new file mode 100644
index 0000000..203c6dd
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/db/ResettableTempFileInputStream.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data.db;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+
+/**
+ * TempFileInputStream that can be reset in order to allow the
+ * ConnectionHelper to retry SQL execution in case of failure.
+ */
+public class ResettableTempFileInputStream extends TempFileInputStream {
+
+    private final FileChannel fileChannel;
+    private long mark = 0;
+
+    public ResettableTempFileInputStream(final File file) throws FileNotFoundException {
+        this(new FileInputStream(file), file);
+    }
+
+    private ResettableTempFileInputStream(final FileInputStream in, final File file) {
+        super(in, file);
+        this.fileChannel = in.getChannel();
+    }
+
+    @Override
+    public boolean markSupported() {
+        return true;
+    }
+
+    @Override
+    public synchronized void mark(int readlimit) {
+        try {
+            mark = fileChannel.position();
+        } catch (IOException ex) {
+            mark = -1;
+        }
+    }
+
+    @Override
+    public synchronized void reset() throws IOException {
+        if (mark == -1) {
+            throw new IOException("Mark failed");
+        }
+        fileChannel.position(mark);
+    }
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/db/TempFileInputStream.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/db/TempFileInputStream.java
new file mode 100644
index 0000000..5419e2c
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/db/TempFileInputStream.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data.db;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FilterInputStream;
+import java.io.IOException;
+
+import org.apache.commons.io.input.ClosedInputStream;
+
+/**
+ * An input stream from a temporary file. The file is deleted when the stream is
+ * closed or garbage collected.
+ */
+public class TempFileInputStream extends FilterInputStream {
+
+    private final File file;
+
+    public TempFileInputStream(File file) throws FileNotFoundException {
+        this(new FileInputStream(file), file);
+    }
+
+    protected TempFileInputStream(FileInputStream in, File file) {
+        super(in);
+        this.file = file;
+    }
+
+    @Override
+    public void close() throws IOException {
+        in.close();
+        in = new ClosedInputStream();
+        file.delete();
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        close();
+        super.finalize();
+    }
+
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/util/NamedThreadFactory.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/util/NamedThreadFactory.java
new file mode 100644
index 0000000..e176db7
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/data/util/NamedThreadFactory.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.core.data.util;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * This class extends {@link ThreadFactory} to creates named threads.
+ */
+public class NamedThreadFactory implements ThreadFactory {
+
+    private AtomicInteger threadCount = new AtomicInteger(1);
+
+    String threadPrefixName;
+
+    public NamedThreadFactory(String threadPrefixName) {
+        super();
+        this.threadPrefixName = threadPrefixName;
+    }
+
+    public Thread newThread(Runnable r) {
+        Thread thread = new Thread(r);
+        thread.setContextClassLoader(getClass().getClassLoader());
+        thread.setName(threadPrefixName + "-" + threadCount.getAndIncrement());
+        return thread;
+    }
+
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/BasedFileSystem.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/BasedFileSystem.java
new file mode 100644
index 0000000..bf3bbdf
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/BasedFileSystem.java
@@ -0,0 +1,187 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.fs;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+
+/**
+ * A <code>BasedFileSystem</code> represents a 'file system in a file system'.
+ */
+public class BasedFileSystem implements FileSystem {
+
+    protected final FileSystem fsBase;
+
+    protected final String basePath;
+
+    /**
+     * Creates a new <code>BasedFileSystem</code>
+     *
+     * @param fsBase      the <code>FileSystem</code> the new file system should be based on
+     * @param relRootPath the root path relative to <code>fsBase</code>'s root
+     */
+    public BasedFileSystem(FileSystem fsBase, String relRootPath) {
+        if (fsBase == null) {
+            throw new IllegalArgumentException("invalid file system argument");
+        }
+        this.fsBase = fsBase;
+
+        if (relRootPath == null) {
+            throw new IllegalArgumentException("invalid null path argument");
+        }
+        if (relRootPath.equals(SEPARATOR)) {
+            throw new IllegalArgumentException("invalid path argument");
+        }
+        if (!relRootPath.startsWith(SEPARATOR)) {
+            relRootPath = SEPARATOR + relRootPath;
+        }
+        if (relRootPath.endsWith(SEPARATOR)) {
+            relRootPath = relRootPath.substring(0, relRootPath.length() - 1);
+
+        }
+        this.basePath = relRootPath;
+    }
+
+    protected String buildBasePath(String path) {
+        if (path.startsWith(SEPARATOR)) {
+            if (path.length() == 1) {
+                return basePath;
+            } else {
+                return basePath + path;
+            }
+        } else {
+            return basePath + SEPARATOR + path;
+        }
+    }
+
+    //-----------------------------------------------------------< FileSystem >
+    /**
+     * {@inheritDoc}
+     */
+    public void init() throws FileSystemException {
+        // check base path
+        if (!fsBase.isFolder(basePath)) {
+            fsBase.createFolder(basePath);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void close() throws FileSystemException {
+        // do nothing; base file system should be closed explicitly
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void createFolder(String folderPath) throws FileSystemException {
+        fsBase.createFolder(buildBasePath(folderPath));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void deleteFile(String filePath) throws FileSystemException {
+        fsBase.deleteFile(buildBasePath(filePath));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void deleteFolder(String folderPath) throws FileSystemException {
+        fsBase.deleteFolder(buildBasePath(folderPath));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean exists(String path) throws FileSystemException {
+        return fsBase.exists(buildBasePath(path));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public InputStream getInputStream(String filePath) throws FileSystemException {
+        return fsBase.getInputStream(buildBasePath(filePath));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public OutputStream getOutputStream(String filePath) throws FileSystemException {
+        return fsBase.getOutputStream(buildBasePath(filePath));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean hasChildren(String path) throws FileSystemException {
+        return fsBase.hasChildren(buildBasePath(path));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isFile(String path) throws FileSystemException {
+        return fsBase.isFile(buildBasePath(path));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isFolder(String path) throws FileSystemException {
+        return fsBase.isFolder(buildBasePath(path));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long lastModified(String path) throws FileSystemException {
+        return fsBase.lastModified(buildBasePath(path));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long length(String filePath) throws FileSystemException {
+        return fsBase.length(buildBasePath(filePath));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String[] list(String folderPath) throws FileSystemException {
+        return fsBase.list(buildBasePath(folderPath));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String[] listFiles(String folderPath) throws FileSystemException {
+        return fsBase.listFiles(buildBasePath(folderPath));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String[] listFolders(String folderPath) throws FileSystemException {
+        return fsBase.listFolders(buildBasePath(folderPath));
+    }
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/FileSystem.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/FileSystem.java
similarity index 100%
rename from jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/FileSystem.java
rename to jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/FileSystem.java
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/FileSystemException.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/FileSystemException.java
similarity index 100%
rename from jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/FileSystemException.java
rename to jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/FileSystemException.java
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/FileSystemFactory.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/FileSystemFactory.java
new file mode 100644
index 0000000..012ab7d
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/FileSystemFactory.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.fs;
+
+import javax.jcr.RepositoryException;
+
+
+/**
+ * Factory interface for creating {@link FileSystem} instances. Used
+ * to decouple the repository internals from the repository configuration
+ * mechanism.
+ */
+public interface FileSystemFactory {
+
+    /**
+     * Creates, initializes, and returns a {@link FileSystem} instance
+     * for use by the repository. Note that no information is passed from
+     * the client, so all required configuration information must be
+     * encapsulated in the factory.
+     *
+     * @return initialized file system
+     * @throws RepositoryException if the file system can not be created
+     */
+    FileSystem getFileSystem() throws RepositoryException;
+
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/FileSystemPathUtil.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/FileSystemPathUtil.java
new file mode 100644
index 0000000..af056fb
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/FileSystemPathUtil.java
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.fs;
+
+import java.io.ByteArrayOutputStream;
+import java.util.BitSet;
+
+
+/**
+ * Utility class for handling paths in a file system.
+ */
+public final class FileSystemPathUtil {
+
+    /**
+     * Array of lowercase hexadecimal characters used in creating hex escapes.
+     */
+    private static final char[] HEX_TABLE = "0123456789abcdef".toCharArray();
+
+    /**
+     * The escape character used to mark hex escape sequences.
+     */
+    private static final char ESCAPE_CHAR = '%';
+
+    /**
+     * The list of characters that are not encoded by the <code>escapeName(String)</code>
+     * and <code>unescape(String)</code> methods. They contains the characters
+     * which can safely be used in file names:
+     */
+    public static final BitSet SAFE_NAMECHARS;
+
+    /**
+     * The list of characters that are not encoded by the <code>escapePath(String)</code>
+     * and <code>unescape(String)</code> methods. They contains the characters
+     * which can safely be used in file paths:
+     */
+    public static final BitSet SAFE_PATHCHARS;
+
+    static {
+        // build list of valid name characters
+        SAFE_NAMECHARS = new BitSet(256);
+        int i;
+        for (i = 'a'; i <= 'z'; i++) {
+            SAFE_NAMECHARS.set(i);
+        }
+        for (i = 'A'; i <= 'Z'; i++) {
+            SAFE_NAMECHARS.set(i);
+        }
+        for (i = '0'; i <= '9'; i++) {
+            SAFE_NAMECHARS.set(i);
+        }
+        SAFE_NAMECHARS.set('-');
+        SAFE_NAMECHARS.set('_');
+        SAFE_NAMECHARS.set('.');
+
+        // build list of valid path characters (includes name characters)
+        SAFE_PATHCHARS = (BitSet) SAFE_NAMECHARS.clone();
+        SAFE_PATHCHARS.set(FileSystem.SEPARATOR_CHAR);
+    }
+
+    /**
+     * private constructor
+     */
+    private FileSystemPathUtil() {
+    }
+
+    /**
+     * Escapes the given string using URL encoding for all bytes not included
+     * in the given set of safe characters.
+     *
+     * @param s the string to escape
+     * @param safeChars set of safe characters (bytes)
+     * @return escaped string
+     */
+    private static String escape(String s, BitSet safeChars) {
+        byte[] bytes = s.getBytes();
+        StringBuilder out = new StringBuilder(bytes.length);
+        for (int i = 0; i < bytes.length; i++) {
+            int c = bytes[i] & 0xff;
+            if (safeChars.get(c) && c != ESCAPE_CHAR) {
+                out.append((char) c);
+            } else {
+                out.append(ESCAPE_CHAR);
+                out.append(HEX_TABLE[(c >> 4) & 0x0f]);
+                out.append(HEX_TABLE[(c) & 0x0f]);
+            }
+        }
+        return out.toString();
+    }
+
+    /**
+     * Encodes the specified <code>path</code>. Same as
+     * <code>{@link #escapeName(String)}</code> except that the separator
+     * character <b><code>/</code></b> is regarded as a legal path character
+     * that needs no escaping.
+     *
+     * @param path the path to encode.
+     * @return the escaped path
+     */
+    public static String escapePath(String path) {
+        return escape(path, SAFE_PATHCHARS);
+    }
+
+    /**
+     * Encodes the specified <code>name</code>. Same as
+     * <code>{@link #escapePath(String)}</code> except that the separator character
+     * <b><code>/</code></b> is regarded as an illegal character that needs
+     * escaping.
+     *
+     * @param name the name to encode.
+     * @return the escaped name
+     */
+    public static String escapeName(String name) {
+        return escape(name, SAFE_NAMECHARS);
+    }
+
+    /**
+     * Decodes the specified path/name.
+     *
+     * @param pathOrName the escaped path/name
+     * @return the unescaped path/name
+     */
+    public static String unescape(String pathOrName) {
+        ByteArrayOutputStream out = new ByteArrayOutputStream(pathOrName.length());
+        for (int i = 0; i < pathOrName.length(); i++) {
+            char c = pathOrName.charAt(i);
+            if (c == ESCAPE_CHAR) {
+                try {
+                    out.write(Integer.parseInt(pathOrName.substring(i + 1, i + 3), 16));
+                } catch (NumberFormatException e) {
+                    IllegalArgumentException iae = new IllegalArgumentException("Failed to unescape escape sequence");
+                    iae.initCause(e);
+                    throw iae;
+                }
+                i += 2;
+            } else {
+                out.write(c);
+            }
+        }
+        return new String(out.toByteArray());
+    }
+
+    /**
+     * Tests whether the specified path represents the root path, i.e. "/".
+     *
+     * @param path path to test
+     * @return true if the specified path represents the root path; false otherwise.
+     */
+    public static boolean denotesRoot(String path) {
+        return path.equals(FileSystem.SEPARATOR);
+    }
+
+    /**
+     * Checks if <code>path</code> is a valid path.
+     *
+     * @param path the path to be checked
+     * @throws FileSystemException If <code>path</code> is not a valid path
+     */
+    public static void checkFormat(String path) throws FileSystemException {
+        if (path == null) {
+            throw new FileSystemException("null path");
+        }
+
+        // path must be absolute, i.e. starting with '/'
+        if (!path.startsWith(FileSystem.SEPARATOR)) {
+            throw new FileSystemException("not an absolute path: " + path);
+        }
+
+        // trailing '/' is not allowed (except for root path)
+        if (path.endsWith(FileSystem.SEPARATOR) && path.length() > 1) {
+            throw new FileSystemException("malformed path: " + path);
+        }
+
+        String[] names = path.split(FileSystem.SEPARATOR);
+        for (int i = 1; i < names.length; i++) {
+            // name must not be empty
+            if (names[i].length() == 0) {
+                throw new FileSystemException("empty name: " + path);
+            }
+            // leading/trailing whitespace is not allowed
+            String trimmed = names[i].trim();
+            if (!trimmed.equals(names[i])) {
+                throw new FileSystemException("illegal leading or trailing whitespace in name: " + path);
+            }
+        }
+    }
+
+    /**
+     * Returns the parent directory of the specified <code>path</code>.
+     *
+     * @param path a file system path denoting a directory or a file.
+     * @return the parent directory.
+     */
+    public static String getParentDir(String path) {
+        int pos = path.lastIndexOf(FileSystem.SEPARATOR_CHAR);
+        if (pos > 0) {
+            return path.substring(0, pos);
+        }
+        return FileSystem.SEPARATOR;
+    }
+
+    /**
+     * Returns the name of the specified <code>path</code>.
+     *
+     * @param path a file system path denoting a directory or a file.
+     * @return the name.
+     */
+    public static String getName(String path) {
+        int pos = path.lastIndexOf(FileSystem.SEPARATOR_CHAR);
+        if (pos != -1) {
+            return path.substring(pos + 1);
+        }
+        return path;
+    }
+
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/FileSystemResource.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/FileSystemResource.java
new file mode 100644
index 0000000..0bcf225
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/FileSystemResource.java
@@ -0,0 +1,226 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.fs;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.core.fs.FileSystemPathUtil;
+
+/**
+ * A <code>FileSystemResource</code> represents a resource (i.e. file) in a
+ * <code>FileSystem</code>.
+ */
+public class FileSystemResource {
+
+    protected final FileSystem fs;
+
+    protected final String path;
+
+    static {
+        // preload FileSystemPathUtil to prevent classloader issues during shutdown
+        FileSystemPathUtil.class.hashCode();
+    }
+
+    /**
+     * Creates a new <code>FileSystemResource</code>
+     *
+     * @param fs   the <code>FileSystem</code> where the resource is located
+     * @param path the path of the resource in the <code>FileSystem</code>
+     */
+    public FileSystemResource(FileSystem fs, String path) {
+        if (fs == null) {
+            throw new IllegalArgumentException("invalid file system argument");
+        }
+        this.fs = fs;
+
+        if (path == null) {
+            throw new IllegalArgumentException("invalid path argument");
+        }
+        this.path = path;
+    }
+
+    /**
+     * Returns the <code>FileSystem</code> where this resource is located.
+     *
+     * @return the <code>FileSystem</code> where this resource is located.
+     */
+    public FileSystem getFileSystem() {
+        return fs;
+    }
+
+    /**
+     * Returns the path of this resource.
+     *
+     * @return the path of this resource.
+     */
+    public String getPath() {
+        return path;
+    }
+
+    /**
+     * Returns the parent directory of this resource.
+     *
+     * @return the parent directory.
+     */
+    public String getParentDir() {
+        return FileSystemPathUtil.getParentDir(path);
+    }
+
+    /**
+     * Returns the name of this resource.
+     *
+     * @return the name.
+     */
+    public String getName() {
+        return FileSystemPathUtil.getName(path);
+    }
+
+    /**
+     * Creates the parent directory of this resource, including any necessary
+     * but nonexistent parent directories.
+     *
+     * @throws FileSystemException
+     */
+    public synchronized void makeParentDirs() throws FileSystemException {
+        String parentDir = getParentDir();
+        if (!fs.exists(parentDir)) {
+            fs.createFolder(parentDir);
+        }
+    }
+
+    /**
+     * Deletes this resource.
+     * Same as <code>{@link #delete(false)}</code>.
+     *
+     * @see FileSystem#deleteFile
+     */
+    public void delete() throws FileSystemException {
+        delete(false);
+    }
+
+    /**
+     * Deletes this resource.
+     *
+     * @param pruneEmptyParentDirs if <code>true</code>, empty parent folders will
+     *                             automatically be deleted
+     * @see FileSystem#deleteFile
+     */
+    public synchronized void delete(boolean pruneEmptyParentDirs) throws FileSystemException {
+        fs.deleteFile(path);
+        if (pruneEmptyParentDirs) {
+            // prune empty parent folders
+            String parentDir = FileSystemPathUtil.getParentDir(path);
+            while (!parentDir.equals(FileSystem.SEPARATOR)
+                    && fs.exists(parentDir)
+                    && !fs.hasChildren(parentDir)) {
+                fs.deleteFolder(parentDir);
+                parentDir = FileSystemPathUtil.getParentDir(parentDir);
+            }
+        }
+    }
+
+    /**
+     * @see FileSystem#exists
+     */
+    public boolean exists() throws FileSystemException {
+        return fs.exists(path);
+    }
+
+    /**
+     * @see FileSystem#getInputStream
+     */
+    public InputStream getInputStream() throws FileSystemException {
+        return fs.getInputStream(path);
+    }
+
+    /**
+     * Spools this resource to the given output stream.
+     *
+     * @param out output stream where to spool the resource
+     * @throws FileSystemException if the input stream for this resource could
+     *                             not be obtained
+     * @throws IOException         if an error occurs while while spooling
+     * @see FileSystem#getInputStream
+     */
+    public void spool(OutputStream out) throws FileSystemException, IOException {
+        InputStream in = fs.getInputStream(path);
+        try {
+            IOUtils.copy(in, out);
+        } finally {
+            IOUtils.closeQuietly(in);
+        }
+    }
+
+    /**
+     * @see FileSystem#getOutputStream
+     */
+    public OutputStream getOutputStream() throws FileSystemException {
+        return fs.getOutputStream(path);
+    }
+
+    /**
+     * @see FileSystem#lastModified
+     */
+    public long lastModified() throws FileSystemException {
+        return fs.lastModified(path);
+    }
+
+    /**
+     * @see FileSystem#length
+     */
+    public long length() throws FileSystemException {
+        return fs.length(path);
+    }
+
+    //-------------------------------------------< java.lang.Object overrides >
+    /**
+     * Returns the path string of this resource. This is just the
+     * string returned by the <code>{@link #getPath}</code> method.
+     *
+     * @return The path string of this resource
+     */
+    public String toString() {
+        return getPath();
+    }
+
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof FileSystemResource) {
+            FileSystemResource other = (FileSystemResource) obj;
+            return (path == null ? other.path == null : path.equals(other.path))
+                    && (fs == null ? other.fs == null : fs.equals(other.fs));
+        }
+        return false;
+    }
+
+    /**
+     * Returns zero to satisfy the Object equals/hashCode contract.
+     * This class is mutable and not meant to be used as a hash key.
+     *
+     * @return always zero
+     * @see Object#hashCode()
+     */
+    public int hashCode() {
+        return 0;
+    }
+
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/RandomAccessOutputStream.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/RandomAccessOutputStream.java
similarity index 100%
rename from jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/RandomAccessOutputStream.java
rename to jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/RandomAccessOutputStream.java
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/local/FileUtil.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/local/FileUtil.java
similarity index 100%
rename from jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/local/FileUtil.java
rename to jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/local/FileUtil.java
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/local/HandleMonitor.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/local/HandleMonitor.java
similarity index 100%
rename from jackrabbit-core/src/main/java/org/apache/jackrabbit/core/fs/local/HandleMonitor.java
rename to jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/local/HandleMonitor.java
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/local/LocalFileSystem.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/local/LocalFileSystem.java
new file mode 100644
index 0000000..d5d32ae
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/fs/local/LocalFileSystem.java
@@ -0,0 +1,388 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.fs.local;
+
+import org.apache.jackrabbit.core.fs.FileSystem;
+import org.apache.jackrabbit.core.fs.FileSystemException;
+import org.apache.jackrabbit.core.fs.local.FileUtil;
+import org.apache.jackrabbit.core.fs.local.HandleMonitor;
+import org.apache.jackrabbit.util.LazyFileInputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * A <code>LocalFileSystem</code> ...
+ */
+public class LocalFileSystem implements FileSystem {
+
+    private static Logger log = LoggerFactory.getLogger(LocalFileSystem.class);
+
+    private File root;
+
+    private HandleMonitor monitor;
+
+    /**
+     * Default constructor
+     */
+    public LocalFileSystem() {
+    }
+
+    public String getPath() {
+        if (root != null) {
+            return root.getPath();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Sets the path to the root directory of this local filesystem. please note
+     * that this method can be called via reflection during initialization and
+     * must not be altered.
+     *
+     * @param rootPath the path to the root directory
+     */
+    public void setPath(String rootPath) {
+        setRoot(new File(osPath(rootPath)));
+    }
+
+    public void setRoot(File root) {
+        this.root = root;
+    }
+
+    /**
+     * Enables/Disables the use of the handle monitor.
+     *
+     * @param enable
+     */
+    public void setEnableHandleMonitor(String enable) {
+        setEnableHandleMonitor(Boolean.valueOf(enable).booleanValue());
+    }
+
+    /**
+     * Enables/Disables the use of the handle monitor.
+     *
+     * @param enable flag
+     */
+    public void setEnableHandleMonitor(boolean enable) {
+        if (enable && monitor == null) {
+            monitor = new HandleMonitor();
+        }
+        if (!enable && monitor != null) {
+            monitor = null;
+        }
+    }
+
+    /**
+     * Returns <code>true</code> if use of the handle monitor is currently
+     * enabled, otherwise returns <code>false</code>.
+     *
+     * @see #setEnableHandleMonitor(boolean)
+     */
+    public String getEnableHandleMonitor() {
+        return monitor == null ? "false" : "true";
+    }
+
+    private String osPath(String genericPath) {
+        if (File.separator.equals(SEPARATOR)) {
+            return genericPath;
+        }
+        return genericPath.replace(SEPARATOR_CHAR, File.separatorChar);
+    }
+
+    //-------------------------------------------< java.lang.Object overrides >
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof LocalFileSystem) {
+            LocalFileSystem other = (LocalFileSystem) obj;
+            if (root == null) {
+                return other.root == null;
+            } else {
+                return root.equals(other.root);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns zero to satisfy the Object equals/hashCode contract.
+     * This class is mutable and not meant to be used as a hash key.
+     *
+     * @return always zero
+     * @see Object#hashCode()
+     */
+    public int hashCode() {
+        return 0;
+    }
+
+    //-----------------------------------------------------------< FileSystem >
+    /**
+     * {@inheritDoc}
+     */
+    public void init() throws FileSystemException {
+        if (root == null) {
+            String msg = "root directory not set";
+            log.debug(msg);
+            throw new FileSystemException(msg);
+        }
+
+        if (root.exists()) {
+            if (!root.isDirectory()) {
+                String msg = "path does not denote a folder";
+                log.debug(msg);
+                throw new FileSystemException(msg);
+            }
+        } else {
+            if (!root.mkdirs()) {
+                String msg = "failed to create root";
+                log.debug(msg);
+                throw new FileSystemException(msg);
+            }
+        }
+        log.info("LocalFileSystem initialized at path " + root.getPath());
+        if (monitor != null) {
+            log.info("LocalFileSystem using handle monitor");
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void close() throws FileSystemException {
+        root = null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void createFolder(String folderPath) throws FileSystemException {
+        File f = new File(root, osPath(folderPath));
+        if (f.exists()) {
+            String msg = f.getPath() + " already exists";
+            log.debug(msg);
+            throw new FileSystemException(msg);
+        }
+        if (!f.mkdirs()) {
+            String msg = "failed to create folder " + f.getPath();
+            log.debug(msg);
+            throw new FileSystemException(msg);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void deleteFile(String filePath) throws FileSystemException {
+        File f = new File(root, osPath(filePath));
+        if (!f.isFile()) {
+            String msg = f.getPath() + " does not denote an existing file";
+            throw new FileSystemException(msg);
+        }
+        try {
+            FileUtil.delete(f);
+        } catch (IOException ioe) {
+            String msg = "failed to delete " + f.getPath();
+            if (monitor != null && monitor.isOpen(f)) {
+                log.error("Unable to delete. There are still open streams.");
+                monitor.dump(f);
+            }
+
+            throw new FileSystemException(msg, ioe);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void deleteFolder(String folderPath) throws FileSystemException {
+        File f = new File(root, osPath(folderPath));
+        if (!f.isDirectory()) {
+            String msg = f.getPath() + " does not denote an existing folder";
+            log.debug(msg);
+            throw new FileSystemException(msg);
+        }
+        try {
+            FileUtil.delete(f);
+        } catch (IOException ioe) {
+            String msg = "failed to delete " + f.getPath();
+            log.debug(msg);
+            throw new FileSystemException(msg, ioe);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean exists(String path) throws FileSystemException {
+        File f = new File(root, osPath(path));
+        return f.exists();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public InputStream getInputStream(String filePath)
+            throws FileSystemException {
+        File f = new File(root, osPath(filePath));
+        try {
+            if (monitor == null) {
+                return new LazyFileInputStream(f);
+            } else {
+                return monitor.open(f);
+            }
+        } catch (FileNotFoundException fnfe) {
+            String msg = f.getPath() + " does not denote an existing file";
+            log.debug(msg);
+            throw new FileSystemException(msg, fnfe);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public OutputStream getOutputStream(String filePath)
+            throws FileSystemException {
+        File f = new File(root, osPath(filePath));
+        try {
+            return new FileOutputStream(f);
+        } catch (FileNotFoundException fnfe) {
+            String msg = "failed to get output stream for " + f.getPath();
+            log.debug(msg);
+            throw new FileSystemException(msg, fnfe);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean hasChildren(String path) throws FileSystemException {
+        File f = new File(root, osPath(path));
+        if (!f.exists()) {
+            String msg = f.getPath() + " does not exist";
+            log.debug(msg);
+            throw new FileSystemException(msg);
+        }
+        if (f.isFile()) {
+            return false;
+        }
+        return (f.list().length > 0);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isFile(String path) throws FileSystemException {
+        File f = new File(root, osPath(path));
+        return f.isFile();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isFolder(String path) throws FileSystemException {
+        File f = new File(root, osPath(path));
+        return f.isDirectory();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long lastModified(String path) throws FileSystemException {
+        File f = new File(root, osPath(path));
+        return f.lastModified();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long length(String filePath) throws FileSystemException {
+        File f = new File(root, osPath(filePath));
+        if (!f.exists()) {
+            return -1;
+        }
+        return f.length();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String[] list(String folderPath) throws FileSystemException {
+        File f = new File(root, osPath(folderPath));
+        String[] entries = f.list();
+        if (entries == null) {
+            String msg = folderPath + " does not denote a folder";
+            log.debug(msg);
+            throw new FileSystemException(msg);
+        }
+        return entries;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String[] listFiles(String folderPath) throws FileSystemException {
+        File folder = new File(root, osPath(folderPath));
+        File[] files = folder.listFiles(new FileFilter() {
+            public boolean accept(File f) {
+                return f.isFile();
+            }
+        });
+        if (files == null) {
+            String msg = folderPath + " does not denote a folder";
+            log.debug(msg);
+            throw new FileSystemException(msg);
+        }
+        String[] entries = new String[files.length];
+        for (int i = 0; i < files.length; i++) {
+            entries[i] = files[i].getName();
+        }
+        return entries;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String[] listFolders(String folderPath) throws FileSystemException {
+        File file = new File(root, osPath(folderPath));
+        File[] folders = file.listFiles(new FileFilter() {
+            public boolean accept(File f) {
+                return f.isDirectory();
+            }
+        });
+        if (folders == null) {
+            String msg = folderPath + " does not denote a folder";
+            log.debug(msg);
+            throw new FileSystemException(msg);
+        }
+        String[] entries = new String[folders.length];
+        for (int i = 0; i < folders.length; i++) {
+            entries[i] = folders[i].getName();
+        }
+        return entries;
+    }
+
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/CheckSchemaOperation.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/CheckSchemaOperation.java
similarity index 100%
rename from jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/CheckSchemaOperation.java
rename to jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/CheckSchemaOperation.java
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionFactory.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionFactory.java
new file mode 100644
index 0000000..c0830c3
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionFactory.java
@@ -0,0 +1,377 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.util.db;
+
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.sql.DataSource;
+
+import org.apache.commons.dbcp.BasicDataSource;
+import org.apache.commons.dbcp.DelegatingConnection;
+import org.apache.commons.pool.impl.GenericObjectPool;
+import org.apache.jackrabbit.core.config.DataSourceConfig;
+import org.apache.jackrabbit.core.config.DataSourceConfig.DataSourceDefinition;
+import org.apache.jackrabbit.core.util.db.DataSourceWrapper;
+import org.apache.jackrabbit.util.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A factory for new database connections.
+ * Supported are regular JDBC drivers, as well as
+ * JNDI resources.
+ *
+ * FIXME: the registry currently is ClassLoader wide. I.e., if you start two repositories
+ * then you share the registered datasources...
+ */
+public final class ConnectionFactory {
+
+    private static final Logger log = LoggerFactory.getLogger(ConnectionFactory.class);
+
+    /**
+     * The lock to protect the fields of this class.
+     */
+    private final Object lock = new Object();
+
+    /**
+     * The data sources without logical name. The keys in the map are based on driver-url-user combination.
+     */
+    private final Map<String, DataSource> keyToDataSource = new HashMap<String, DataSource>();
+
+    /**
+     * The configured data sources with logical name. The keys in the map are the logical name.
+     */
+    private final Map<String, DataSource> nameToDataSource = new HashMap<String, DataSource>();
+
+    /**
+     * The configured data source defs. The keys in the map are the logical name.
+     */
+    private final Map<String, DataSourceDefinition> nameToDataSourceDef = new HashMap<String, DataSourceDefinition>();
+
+    /**
+     * The list of data sources created by this factory.
+     */
+    private final List<BasicDataSource> created = new ArrayList<BasicDataSource>();
+
+    private boolean closed = false;
+
+    /**
+     * Registers a number of data sources.
+     *
+     * @param dsc the {@link DataSourceConfig} which contains the configuration
+     */
+    public void registerDataSources(DataSourceConfig dsc) throws RepositoryException {
+        synchronized (lock) {
+            sanityCheck();
+            for (DataSourceDefinition def : dsc.getDefinitions()) {
+                Class<?> driverClass = getDriverClass(def.getDriver());
+                if (driverClass != null
+                        && Context.class.isAssignableFrom(driverClass)) {
+                    DataSource ds = getJndiDataSource((Class<Context>) driverClass, def.getUrl());
+                    nameToDataSource.put(def.getLogicalName(), ds);
+                    nameToDataSourceDef.put(def.getLogicalName(), def);
+                } else {
+                    BasicDataSource bds =
+                        getDriverDataSource(driverClass, def.getUrl(), def.getUser(), def.getPassword());
+                    if (def.getMaxPoolSize() > 0) {
+                        bds.setMaxActive(def.getMaxPoolSize());
+                    }
+                    if (def.getValidationQuery() != null && !"".equals(def.getValidationQuery().trim())) {
+                        bds.setValidationQuery(def.getValidationQuery());
+                    }
+                    nameToDataSource.put(def.getLogicalName(), bds);
+                    nameToDataSourceDef.put(def.getLogicalName(), def);
+                }
+            }
+        }
+    }
+
+    /**
+     * Retrieves a configured data source by logical name.
+     *
+     * @param logicalName the name of the {@code DataSource}
+     * @return a {@code DataSource}
+     * @throws RepositoryException if there is no {@code DataSource} with the given name
+     */
+    public DataSource getDataSource(String logicalName) throws RepositoryException {
+        synchronized (lock) {
+            sanityCheck();
+            DataSource ds = nameToDataSource.get(logicalName);
+            if (ds == null) {
+                throw new RepositoryException("DataSource with logicalName " + logicalName
+                        + " has not been configured");
+            }
+            return ds;
+        }
+    }
+
+    /**
+     * @param logicalName the name of the {@code DataSource}
+     * @return the configured database type
+     * @throws RepositoryException if there is no {@code DataSource} with the given name
+     */
+    public String getDataBaseType(String logicalName) throws RepositoryException {
+        synchronized (lock) {
+            sanityCheck();
+            DataSourceDefinition def = nameToDataSourceDef.get(logicalName);
+            if (def == null) {
+                throw new RepositoryException("DataSource with logicalName " + logicalName
+                        + " has not been configured");
+            }
+            return def.getDbType();
+        }
+    }
+
+    /**
+     * Retrieve a {@code DataSource} for the specified properties.
+     * This can be a JNDI Data Source as well. To do that,
+     * the driver class name must reference a {@code javax.naming.Context} class
+     * (for example {@code javax.naming.InitialContext}), and the URL must be the JNDI URL
+     * (for example {@code java:comp/env/jdbc/Test}).
+     *
+     * @param driver the JDBC driver or the Context class
+     * @param url the database URL
+     * @param user the user name
+     * @param password the password
+     * @return the {@code DataSource}
+     * @throws RepositoryException if the driver could not be loaded
+     * @throws SQLException if the connection could not be established
+     */
+    public DataSource getDataSource(String driver, String url, String user, String password)
+            throws RepositoryException, SQLException    {
+        final String key = driver + url + user;
+        synchronized(lock) {
+            sanityCheck();
+            DataSource ds = keyToDataSource.get(key);
+            if (ds == null) {
+                ds = createDataSource(
+                        driver, url, user, Base64.decodeIfEncoded(password));
+                keyToDataSource.put(key, ds);
+            }
+            return ds;
+        }
+    }
+
+    /**
+     *
+     */
+    public void close() {
+        synchronized(lock) {
+            sanityCheck();
+            for (BasicDataSource ds : created) {
+                try {
+                    ds.close();
+                } catch (SQLException e) {
+                    log.error("failed to close " + ds, e);
+                }
+            }
+            keyToDataSource.clear();
+            nameToDataSource.clear();
+            nameToDataSourceDef.clear();
+            created.clear();
+            closed = true;
+        }
+    }
+
+    /**
+     * Needed for pre-10R2 Oracle blob support....:(
+     *
+     * This method actually assumes that we are using commons DBCP 1.2.2.
+     *
+     * @param con the commons-DBCP {@code DelegatingConnection} to unwrap
+     * @return the unwrapped connection
+     */
+    public static Connection unwrap(Connection con) throws SQLException {
+        if (con instanceof DelegatingConnection) {
+            return ((DelegatingConnection)con).getInnermostDelegate();
+        } else {
+            throw new SQLException("failed to unwrap connection of class " + con.getClass().getName() +
+                ", expected it to be a " + DelegatingConnection.class.getName());
+        }
+    }
+
+    private void sanityCheck() {
+        if (closed) {
+            throw new IllegalStateException("this factory has already been closed");
+        }
+    }
+
+    /**
+     * Create a new pooling data source or finds an existing JNDI data source (depends on driver).
+     *
+     * @param driver
+     * @param url
+     * @param user
+     * @param password
+     * @return
+     * @throws RepositoryException
+     */
+    private DataSource createDataSource(String driver, String url, String user, String password)
+            throws RepositoryException {
+        Class<?> driverClass = getDriverClass(driver);
+        if (driverClass != null
+                && Context.class.isAssignableFrom(driverClass)) {
+            @SuppressWarnings("unchecked")
+            DataSource database = getJndiDataSource((Class<Context>) driverClass, url);
+            if (user == null && password == null) {
+                return database;
+            } else {
+                return new DataSourceWrapper(database, user, password);
+            }
+        } else {
+            return getDriverDataSource(driverClass, url, user, password);
+        }
+    }
+
+    /**
+     * Loads and returns the given JDBC driver (or JNDI context) class.
+     * Returns <code>null</code> if a class name is not given.
+     *
+     * @param driver driver class name
+     * @return driver class, or <code>null</code>
+     * @throws RepositoryException if the class can not be loaded
+     */
+    private Class<?> getDriverClass(String driver)
+            throws RepositoryException {
+        try {
+            if (driver != null && driver.length() > 0) {
+                return Class.forName(driver);
+            } else {
+                return null;
+            }
+        } catch (ClassNotFoundException e) {
+            throw new RepositoryException(
+                    "Could not load JDBC driver class " + driver, e);
+        }
+    }
+
+    /**
+     * Returns the JDBC {@link DataSource} bound to the given name in
+     * the JNDI {@link Context} identified by the given class.
+     *
+     * @param contextClass class that is instantiated to get the JNDI context
+     * @param name name of the DataSource within the JNDI context
+     * @return the DataSource bound in JNDI
+     * @throws RepositoryException if the JNDI context can not be accessed,
+     *                             or if the named DataSource is not found
+     */
+    private DataSource getJndiDataSource(
+            Class<Context> contextClass, String name)
+            throws RepositoryException {
+        try {
+            Object object = contextClass.newInstance().lookup(name);
+            if (object instanceof DataSource) {
+                return (DataSource) object;
+            } else {
+                throw new RepositoryException(
+                        "Object " + object + " with JNDI name "
+                        + name + " is not a JDBC DataSource");
+            }
+        } catch (InstantiationException e) {
+            throw new RepositoryException(
+                    "Invalid JNDI context: " + contextClass.getName(), e);
+        } catch (IllegalAccessException e) {
+            throw new RepositoryException(
+                    "Invalid JNDI context: " + contextClass.getName(), e);
+        } catch (NamingException e) {
+            throw new RepositoryException(
+                    "JNDI name not found: " + name, e);
+        }
+    }
+
+    /**
+     * Creates and returns a pooling JDBC {@link DataSource} for accessing
+     * the database identified by the given driver class and JDBC
+     * connection URL. The driver class can be <code>null</code> if
+     * a specific driver has not been configured.
+     *
+     * @param driverClass the JDBC driver class, or <code>null</code>
+     * @param url the JDBC connection URL
+     * @return pooling DataSource for accessing the specified database
+     */
+    private BasicDataSource getDriverDataSource(
+            Class<?> driverClass, String url, String user, String password) {
+        BasicDataSource ds = new BasicDataSource();
+        created.add(ds);
+
+        if (driverClass != null) {
+        	Driver instance = null;
+            try {
+                // Workaround for Apache Derby:
+                // The JDBC specification recommends the Class.forName
+                // method without the .newInstance() method call,
+                // but it is required after a Derby 'shutdown'
+                instance = (Driver) driverClass.newInstance();
+            } catch (Throwable e) {
+                // Ignore exceptions as there's no requirement for
+                // a JDBC driver class to have a public default constructor
+            }
+            if (instance != null) {
+                if (instance.jdbcCompliant()) {
+                	// JCR-3445 At the moment the PostgreSQL isn't compliant because it doesn't implement this method...                	
+                    ds.setValidationQueryTimeout(3);
+                }
+            }
+            ds.setDriverClassName(driverClass.getName());
+        }
+
+        ds.setUrl(url);
+        ds.setUsername(user);
+        ds.setPassword(password);
+        ds.setDefaultAutoCommit(true);
+        ds.setTestOnBorrow(false);
+        ds.setTestWhileIdle(true);
+        ds.setTimeBetweenEvictionRunsMillis(600000); // 10 Minutes
+        ds.setMinEvictableIdleTimeMillis(60000); // 1 Minute
+        ds.setMaxActive(-1); // unlimited
+        ds.setMaxIdle(GenericObjectPool.DEFAULT_MAX_IDLE + 10);
+        ds.setValidationQuery(guessValidationQuery(url));
+        ds.setAccessToUnderlyingConnectionAllowed(true);
+        ds.setPoolPreparedStatements(true);
+        ds.setMaxOpenPreparedStatements(-1); // unlimited
+        return ds;
+    }
+
+    private String guessValidationQuery(String url) {
+        if (url.contains("derby")) {
+            return "values(1)";
+        } else if (url.contains("mysql")) {
+            return "select 1";
+        } else if (url.contains("sqlserver") || url.contains("jtds")) {
+            return "select 1";
+        } else if (url.contains("oracle")) {
+            return "select 'validationQuery' from dual";
+        } else if (url.contains("postgresql")) {
+            return "select 1";
+        } else if (url.contains("h2")) {
+            return "select 1";
+        } else if (url.contains("db2")) {
+            return "values(1)";
+        }
+        log.warn("Failed to guess validation query for URL " + url);
+        return null;
+    }
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionHelper.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionHelper.java
new file mode 100644
index 0000000..ace63c4
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/ConnectionHelper.java
@@ -0,0 +1,599 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.util.db;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.sql.DataSource;
+
+import org.apache.jackrabbit.data.core.TransactionContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class provides convenience methods to execute SQL statements. They can be either executed in isolation
+ * or within the context of a JDBC transaction; the so-called <i>batch mode</i> (use the {@link #startBatch()}
+ * and {@link #endBatch(boolean)} methods for this).
+ *
+ * <p>
+ *
+ * This class contains logic to retry execution of SQL statements. If this helper is <i>not</i> in batch mode
+ * and if a statement fails due to an {@code SQLException}, then it is retried. If the {@code block} argument
+ * of the constructor call was {@code false} then it is retried only once. Otherwise the statement is retried
+ * until either it succeeds or the thread is interrupted. This clearly assumes that the only cause of {@code
+ * SQLExceptions} is faulty {@code Connections} which are restored eventually. <br/> <strong>Note</strong>:
+ * This retry logic only applies to the following methods:
+ * <ul>
+ * <li>{@link #exec(String, Object...)}</li>
+ * <li>{@link #update(String, Object[])}</li>
+ * <li>{@link #exec(String, Object[], boolean, int)}</li>
+ * </ul>
+ *
+ * <p>
+ *
+ * This class is not thread-safe and if it is to be used by multiple threads then the clients must make sure
+ * that access to this class is properly synchronized.
+ *
+ * <p>
+ *
+ * <strong>Implementation note</strong>: The {@code Connection} that is retrieved from the {@code DataSource}
+ * in {@link #getConnection()} may be broken. This is so because if an internal {@code DataSource} is used,
+ * then this is a commons-dbcp {@code DataSource} with a <code>testWhileIdle</code> validation strategy (see
+ * the {@code ConnectionFactory} class). Furthermore, if it is a {@code DataSource} obtained through JNDI then we
+ * can make no assumptions about the validation strategy. This means that our retry logic must either assume that
+ * the SQL it tries to execute can do so without errors (i.e., the statement is valid), or it must implement its
+ * own validation strategy to apply. Currently, the former is in place.
+ */
+public class ConnectionHelper {
+
+    static Logger log = LoggerFactory.getLogger(ConnectionHelper.class);
+
+    private static final int RETRIES = 1;
+
+    private static final int SLEEP_BETWEEN_RETRIES_MS = 100;
+
+    final boolean blockOnConnectionLoss;
+
+    private final boolean checkTablesWithUserName;
+
+    protected final DataSource dataSource;
+
+    private Map<Object, Connection> batchConnectionMap = Collections.synchronizedMap(new HashMap<Object, Connection>());
+
+    /**
+     * The default fetchSize is '0'. This means the fetchSize Hint will be ignored 
+     */
+    private int fetchSize = 0;
+
+    /**
+     * @param dataSrc the {@link DataSource} on which this instance acts
+     * @param block whether the helper should transparently block on DB connection loss (otherwise it retries
+     *            once and if that fails throws exception)
+     */
+    public ConnectionHelper(DataSource dataSrc, boolean block) {
+        dataSource = dataSrc;
+        checkTablesWithUserName = false;
+        blockOnConnectionLoss = block;
+    }
+
+    /**
+     * @param dataSrc the {@link DataSource} on which this instance acts
+     * @param checkWithUserName whether the username is to be used for the {@link #tableExists(String)} method
+     * @param block whether the helper should transparently block on DB connection loss (otherwise it throws exceptions)
+     */
+    protected ConnectionHelper(DataSource dataSrc, boolean checkWithUserName, boolean block) {
+        dataSource = dataSrc;
+        checkTablesWithUserName = checkWithUserName;
+        blockOnConnectionLoss = block;
+    }
+
+    /**
+     * @param dataSrc the {@link DataSource} on which this instance acts
+     * @param checkWithUserName whether the username is to be used for the {@link #tableExists(String)} method
+     * @param block whether the helper should transparently block on DB connection loss (otherwise it throws exceptions)
+     * @param fetchSize the fetchSize that will be used per default
+     */
+    protected ConnectionHelper(DataSource dataSrc, boolean checkWithUserName, boolean block, int fetchSize) {
+        dataSource = dataSrc;
+        checkTablesWithUserName = checkWithUserName;
+        blockOnConnectionLoss = block;
+        this.fetchSize = fetchSize;
+    }
+
+    /**
+     * A utility method that makes sure that <code>identifier</code> does only consist of characters that are
+     * allowed in names on the target database. Illegal characters will be escaped as necessary.
+     *
+     * This method is not affected by the
+     *
+     * @param identifier the identifier to convert to a db specific identifier
+     * @return the db-normalized form of the given identifier
+     * @throws SQLException if an error occurs
+     */
+    public final String prepareDbIdentifier(String identifier) throws SQLException {
+        if (identifier == null) {
+            return null;
+        }
+        String legalChars = "ABCDEFGHIJKLMNOPQRSTUVWXZY0123456789_";
+        legalChars += getExtraNameCharacters();
+        String id = identifier.toUpperCase();
+        StringBuilder escaped = new StringBuilder();
+        for (int i = 0; i < id.length(); i++) {
+            char c = id.charAt(i);
+            if (legalChars.indexOf(c) == -1) {
+                replaceCharacter(escaped, c);
+            } else {
+                escaped.append(c);
+            }
+        }
+        return escaped.toString();
+    }
+
+    /**
+     * Called from {@link #prepareDbIdentifier(String)}. Default implementation replaces the illegal
+     * characters with their hexadecimal encoding.
+     *
+     * @param escaped the escaped db identifier
+     * @param c the character to replace
+     */
+    protected void replaceCharacter(StringBuilder escaped, char c) {
+        escaped.append("_x");
+        String hex = Integer.toHexString(c);
+        escaped.append("0000".toCharArray(), 0, 4 - hex.length());
+        escaped.append(hex);
+        escaped.append("_");
+    }
+
+    /**
+     * Returns true if we are currently in a batch mode, false otherwise.
+     * 
+     * @return true if the current thread or the active transaction is running in batch mode, false otherwise.
+     */
+    protected boolean inBatchMode() {
+    	return getTransactionAwareBatchConnection() != null;
+    }
+
+	/**
+     * The default implementation returns the {@code extraNameCharacters} provided by the databases metadata.
+     *
+     * @return the additional characters for identifiers supported by the db
+     * @throws SQLException on error
+     */
+    private String getExtraNameCharacters() throws SQLException {
+        Connection con = dataSource.getConnection();
+        try {
+            DatabaseMetaData metaData = con.getMetaData();
+            return metaData.getExtraNameCharacters();
+        } finally {
+            DbUtility.close(con, null, null);
+        }
+    }
+
+    /**
+     * Checks whether the given table exists in the database.
+     *
+     * @param tableName the name of the table
+     * @return whether the given table exists
+     * @throws SQLException on error
+     */
+    public final boolean tableExists(String tableName) throws SQLException {
+        Connection con = dataSource.getConnection();
+        ResultSet rs = null;
+        boolean schemaExists = false;
+        String name = tableName;
+        try {
+            DatabaseMetaData metaData = con.getMetaData();
+            if (metaData.storesLowerCaseIdentifiers()) {
+                name = tableName.toLowerCase();
+            } else if (metaData.storesUpperCaseIdentifiers()) {
+                name = tableName.toUpperCase();
+            }
+            String userName = null;
+            if (checkTablesWithUserName) {
+                userName = metaData.getUserName();
+            }
+            rs = metaData.getTables(null, userName, name, null);
+            schemaExists = rs.next();
+        } finally {
+            DbUtility.close(con, null, rs);
+        }
+        return schemaExists;
+    }
+
+    /**
+     * Starts the <i>batch mode</i>. If an {@link SQLException} is thrown, then the batch mode is not started. <p/>
+     * <strong>Important:</strong> clients that call this method must make sure that
+     * {@link #endBatch(boolean)} is called eventually.
+     *
+     * @throws SQLException on error
+     */
+    public final void startBatch() throws SQLException {
+        if (inBatchMode()) {
+            throw new SQLException("already in batch mode");
+        }
+        Connection batchConnection = null;
+        try {
+            batchConnection = getConnection(false);
+            batchConnection.setAutoCommit(false);
+            setTransactionAwareBatchConnection(batchConnection);
+        } catch (SQLException e) {
+            removeTransactionAwareBatchConnection();
+            // Strive for failure atomicity
+            if (batchConnection != null) {
+                DbUtility.close(batchConnection, null, null);
+            }
+            throw e;
+        }
+    }
+
+	/**
+     * This method always ends the <i>batch mode</i>.
+     *
+     * @param commit whether the changes in the batch should be committed or rolled back
+     * @throws SQLException if the commit or rollback of the underlying JDBC Connection threw an {@code
+     *             SQLException}
+     */
+    public final void endBatch(boolean commit) throws SQLException {
+        if (!inBatchMode()) {
+            throw new SQLException("not in batch mode");
+        }
+        Connection batchConnection = getTransactionAwareBatchConnection(); 
+        try {
+            if (commit) {
+            	batchConnection.commit();
+            } else {
+            	batchConnection.rollback();
+            }
+        } finally {
+            removeTransactionAwareBatchConnection();
+            if (batchConnection != null) {
+            	DbUtility.close(batchConnection, null, null);
+            }
+        }
+    }
+
+    /**
+     * Executes a general SQL statement and immediately closes all resources.
+     *
+     * Note: We use a Statement if there are no parameters to avoid a problem on
+     * the Oracle 10g JDBC driver w.r.t. :NEW and :OLD keywords that triggers ORA-17041.
+     *
+     * @param sql an SQL statement string
+     * @param params the parameters for the SQL statement
+     * @throws SQLException on error
+     */
+    public final void exec(final String sql, final Object... params) throws SQLException {
+        new RetryManager<Void>(params) {
+
+            @Override
+            protected Void call() throws SQLException {
+                reallyExec(sql, params);
+                return null;
+            }
+
+        }.doTry();
+    }
+
+    void reallyExec(String sql, Object... params) throws SQLException {
+        Connection con = null;
+        Statement stmt = null;
+        boolean inBatchMode = inBatchMode();
+        try {
+            con = getConnection(inBatchMode);
+            if (params == null || params.length == 0) {
+                stmt = con.createStatement();
+                stmt.execute(sql);
+            } else {
+                PreparedStatement p = con.prepareStatement(sql);
+                stmt = p;
+                execute(p, params);
+            }
+        } finally {
+            closeResources(con, stmt, null, inBatchMode);
+        }
+    }
+
+    /**
+     * Executes an update or delete statement and returns the update count.
+     *
+     * @param sql an SQL statement string
+     * @param params the parameters for the SQL statement
+     * @return the update count
+     * @throws SQLException on error
+     */
+    public final int update(final String sql, final Object... params) throws SQLException {
+        return new RetryManager<Integer>(params) {
+
+            @Override
+            protected Integer call() throws SQLException {
+                return reallyUpdate(sql, params);
+            }
+
+        }.doTry();
+    }
+
+    int reallyUpdate(String sql, Object... params) throws SQLException {
+        Connection con = null;
+        PreparedStatement stmt = null;
+        boolean inBatchMode = inBatchMode();
+        try {
+            con = getConnection(inBatchMode);
+            stmt = con.prepareStatement(sql);
+            return execute(stmt, params).getUpdateCount();
+        } finally {
+            closeResources(con, stmt, null, inBatchMode);
+        }
+    }
+
+    /**
+     * Executes a SQL query and returns the {@link ResultSet}. The
+     * returned {@link ResultSet} should be closed by clients.
+     *
+     * @param sql an SQL statement string
+     * @param params the parameters for the SQL statement
+     * @return a {@link ResultSet}
+     */
+    public final ResultSet query(String sql, Object... params) throws SQLException {
+        return exec(sql, params, false, 0);
+    }
+
+    /**
+     * Executes a general SQL statement and returns the {@link ResultSet} of the executed statement. The
+     * returned {@link ResultSet} should be closed by clients.
+     *
+     * @param sql an SQL statement string
+     * @param params the parameters for the SQL statement
+     * @param returnGeneratedKeys whether generated keys should be returned
+     * @param maxRows the maximum number of rows in a potential {@link ResultSet} (0 means no limit)
+     * @return a {@link ResultSet}
+     * @throws SQLException on error
+     */
+    public final ResultSet exec(final String sql, final Object[] params, final boolean returnGeneratedKeys,
+            final int maxRows) throws SQLException {
+        return new RetryManager<ResultSet>(params) {
+
+            @Override
+            protected ResultSet call() throws SQLException {
+            	return reallyExec(sql, params, returnGeneratedKeys, maxRows);
+            }
+
+        }.doTry();
+    }
+
+    ResultSet reallyExec(String sql, Object[] params, boolean returnGeneratedKeys, int maxRows)
+            throws SQLException {
+        Connection con = null;
+        PreparedStatement stmt = null;
+        ResultSet rs = null;
+        boolean inBatchMode = inBatchMode();
+        try {
+            con = getConnection(inBatchMode);
+            if (returnGeneratedKeys) {
+                stmt = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
+            } else {
+                stmt = con.prepareStatement(sql);
+            }
+            stmt.setMaxRows(maxRows);
+            int currentFetchSize = this.fetchSize;
+            if (0 < maxRows && maxRows < currentFetchSize) {
+            	currentFetchSize = maxRows; // JCR-3090
+            }
+            stmt.setFetchSize(currentFetchSize);
+            execute(stmt, params);
+            if (returnGeneratedKeys) {
+                rs = stmt.getGeneratedKeys();
+            } else {
+                rs = stmt.getResultSet();
+            }
+            // Don't wrap null
+            if (rs == null) {
+            	closeResources(con, stmt, rs, inBatchMode);
+                return null;
+            }
+            if (inBatchMode) {
+                return ResultSetWrapper.newInstance(null, stmt, rs);
+            } else {
+                return ResultSetWrapper.newInstance(con, stmt, rs);
+            }
+        } catch (SQLException e) {
+            closeResources(con, stmt, rs, inBatchMode);
+            throw e;
+        }
+    }
+
+	/**
+     * Gets a connection based on the {@code batchMode} state of this helper. The connection should be closed
+     * by a call to {@link #closeResources(Connection, Statement, ResultSet)} which also takes the {@code
+     * batchMode} state into account.
+     *
+     * @param inBatchMode indicates if we are in a batchMode
+     * @return a {@code Connection} to use, based on the batch mode state
+     * @throws SQLException on error
+     */
+    protected final Connection getConnection(boolean inBatchMode) throws SQLException {
+        if (inBatchMode) {
+            return getTransactionAwareBatchConnection();
+        } else {
+            Connection con = dataSource.getConnection();
+            // JCR-1013: Setter may fail unnecessarily on a managed connection
+            if (!con.getAutoCommit()) {
+                con.setAutoCommit(true);
+            }
+            return con;
+        }
+    }
+
+    /**
+     * Returns the Batch Connection.
+     * 
+     * @return Connection
+     */
+    private Connection getTransactionAwareBatchConnection() {
+    	Object threadId = TransactionContext.getCurrentThreadId();
+       	return batchConnectionMap.get(threadId);
+	}
+
+    /**
+     * Stores the given Connection to the batchConnectionMap.
+     * If we are running in a XA Environment the globalTransactionId will be used as Key.
+     * In Non-XA Environment the ThreadName is used.
+     * 
+     * @param batchConnection
+     */
+	private void setTransactionAwareBatchConnection(Connection batchConnection) {
+    	Object threadId = TransactionContext.getCurrentThreadId();
+    	batchConnectionMap.put(threadId, batchConnection);
+	}
+
+    /**
+     * Removes the Batch Connection from the batchConnectionMap
+     */
+	private void removeTransactionAwareBatchConnection() {
+    	Object threadId = TransactionContext.getCurrentThreadId();
+    	batchConnectionMap.remove(threadId);
+	}
+	
+	/**
+     * Closes the given resources given the {@code batchMode} state.
+     *
+     * @param con the {@code Connection} obtained through the {@link #getConnection()} method
+     * @param stmt a {@code Statement}
+     * @param rs a {@code ResultSet}
+     * @param inBatchMode indicates if we are in a batchMode
+     */
+    protected final void closeResources(Connection con, Statement stmt, ResultSet rs, boolean inBatchMode) {
+        if (inBatchMode) {
+            DbUtility.close(null, stmt, rs);
+        } else {
+            DbUtility.close(con, stmt, rs);
+        }
+    }
+
+    /**
+     * This method is used by all methods of this class that execute SQL statements. This default
+     * implementation sets all parameters and unwraps {@link StreamWrapper} instances. Subclasses may override
+     * this method to do something special with the parameters. E.g., the {@link Oracle10R1ConnectionHelper}
+     * overrides it in order to add special blob handling.
+     *
+     * @param stmt the {@link PreparedStatement} to execute
+     * @param params the parameters
+     * @return the executed statement
+     * @throws SQLException on error
+     */
+    protected PreparedStatement execute(PreparedStatement stmt, Object[] params) throws SQLException {
+        for (int i = 0; params != null && i < params.length; i++) {
+            Object p = params[i];
+            if (p instanceof StreamWrapper) {
+                StreamWrapper wrapper = (StreamWrapper) p;
+                stmt.setBinaryStream(i + 1, wrapper.getStream(), (int) wrapper.getSize());
+            } else {
+                stmt.setObject(i + 1, p);
+            }
+        }
+        stmt.execute();
+        return stmt;
+    }
+
+    /**
+     * This class encapsulates the logic to retry a method invocation if it threw an SQLException.
+     * The RetryManager must cleanup the Params it will get.
+     *
+     * @param <T> the return type of the method which is retried if it failed
+     */
+    public abstract class RetryManager<T> {
+
+    	private Object[] params;
+    	
+    	public RetryManager(Object[] params) {
+    		this.params = params;
+    	}
+    	
+        public final T doTry() throws SQLException {
+            try {
+                if (inBatchMode()) {
+                    return call();
+                } else {
+                    boolean sleepInterrupted = false;
+                    int failures = 0;
+                    SQLException lastException = null;
+                    while (!sleepInterrupted && (blockOnConnectionLoss || failures <= RETRIES)) {
+                        try {
+                            return call();
+                        } catch (SQLException e) {
+                            lastException = e;
+                        }
+                        log.error("Failed to execute SQL (stacktrace on DEBUG log level): " + lastException);
+                        log.debug("Failed to execute SQL", lastException);
+                        if (!resetParamResources()) {
+                            log.warn("Could not reset parameters: not retrying SQL call");
+                            break;
+                        }
+                        failures++;
+                        if (blockOnConnectionLoss || failures <= RETRIES) { // if we're going to try again
+                            try {
+                                Thread.sleep(SLEEP_BETWEEN_RETRIES_MS);
+                            } catch (InterruptedException e1) {
+                                Thread.currentThread().interrupt();
+                                sleepInterrupted = true;
+                                log.error("Interrupted: canceling retry");
+                            }
+                        }
+                    }
+                    throw lastException;
+                }
+            } finally {
+                cleanupParamResources();
+            }
+        }
+
+        protected abstract T call() throws SQLException;
+
+		/**
+		 * Cleans up the Parameter resources that are not automatically closed or deleted.
+		 */
+		protected void cleanupParamResources() {
+		    for (int i = 0; params != null && i < params.length; i++) {
+		        Object p = params[i];
+		        if (p instanceof StreamWrapper) {
+		            StreamWrapper wrapper = (StreamWrapper) p;
+		            wrapper.closeStream();
+		        }
+		    }
+		}
+
+        protected boolean resetParamResources() {
+            for (int i = 0; params != null && i < params.length; i++) {
+                Object p = params[i];
+                if (p instanceof StreamWrapper) {
+                    StreamWrapper wrapper = (StreamWrapper) p;
+                    if(!wrapper.resetStream()) {
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+    }
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/DataSourceWrapper.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DataSourceWrapper.java
similarity index 100%
rename from jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/DataSourceWrapper.java
rename to jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DataSourceWrapper.java
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/DatabaseAware.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DatabaseAware.java
similarity index 100%
rename from jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/DatabaseAware.java
rename to jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DatabaseAware.java
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DbUtility.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DbUtility.java
new file mode 100644
index 0000000..90c9ee2
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DbUtility.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.util.db;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class contains some database utility methods.
+ */
+public final class DbUtility {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DbUtility.class);
+
+    /**
+     * Private constructor for utility class pattern.
+     */
+    private DbUtility() {
+    }
+
+    /**
+     * This is a utility method which closes the given resources without throwing exceptions. Any exceptions
+     * encountered are logged instead.
+     * 
+     * @param rs the {@link ResultSet} to close, may be null
+     */
+    public static void close(ResultSet rs) {
+        close(null, null, rs);
+    }
+
+    /**
+     * This is a utility method which closes the given resources without throwing exceptions. Any exceptions
+     * encountered are logged instead.
+     * 
+     * @param con the {@link Connection} to close, may be null
+     * @param stmt the {@link Statement} to close, may be null
+     * @param rs the {@link ResultSet} to close, may be null
+     */
+    public static void close(Connection con, Statement stmt, ResultSet rs) {
+        try {
+            if (rs != null) {
+                rs.close();
+            }
+        } catch (SQLException e) {
+            logException("failed to close ResultSet", e);
+        } finally {
+            try {
+                if (stmt != null) {
+                    stmt.close();
+                }
+            } catch (SQLException e) {
+                logException("failed to close Statement", e);
+            } finally {
+                try {
+                    if (con != null && !con.isClosed()) {
+                        con.close();
+                    }
+                } catch (SQLException e) {
+                    logException("failed to close Connection", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Logs an SQL exception on error level, and debug level (more detail).
+     * 
+     * @param message the message
+     * @param e the exception
+     */
+    public static void logException(String message, SQLException e) {
+        if (message != null) {
+            LOG.error(message);
+        }
+        LOG.error("       Reason: " + e.getMessage());
+        LOG.error("   State/Code: " + e.getSQLState() + "/" + e.getErrorCode());
+        LOG.debug("   dump:", e);
+    }
+}
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/DerbyConnectionHelper.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DerbyConnectionHelper.java
similarity index 100%
rename from jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/DerbyConnectionHelper.java
rename to jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/DerbyConnectionHelper.java
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/Oracle10R1ConnectionHelper.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/Oracle10R1ConnectionHelper.java
similarity index 100%
rename from jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/Oracle10R1ConnectionHelper.java
rename to jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/Oracle10R1ConnectionHelper.java
diff --git a/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/OracleConnectionHelper.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/OracleConnectionHelper.java
similarity index 100%
rename from jackrabbit-core/src/main/java/org/apache/jackrabbit/core/util/db/OracleConnectionHelper.java
rename to jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/OracleConnectionHelper.java
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/PostgreSQLConnectionHelper.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/PostgreSQLConnectionHelper.java
new file mode 100644
index 0000000..872d199
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/PostgreSQLConnectionHelper.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.util.db;
+
+import javax.sql.DataSource;
+
+
+/**
+ * The connection helper for PSQL databases. It has special fetch size handling.
+ */
+public final class PostgreSQLConnectionHelper extends ConnectionHelper {
+
+    /**
+     * @param dataSrc the {@code DataSource} on which this helper acts
+     * @param block whether to block on connection loss until the db is up again
+     */
+    public PostgreSQLConnectionHelper(DataSource dataSrc, boolean block) {
+        super(dataSrc, false, block, 10000);
+    }
+
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/ResultSetWrapper.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/ResultSetWrapper.java
new file mode 100644
index 0000000..2b17146
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/ResultSetWrapper.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.util.db;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.Statement;
+
+
+/**
+ * This is a dynamic proxy in order to support both Java 5 and 6.
+ */
+public final class ResultSetWrapper implements InvocationHandler {
+
+    private final Connection connection;
+
+    private final Statement statement;
+
+    private final ResultSet resultSet;
+
+    /**
+     * Creates a new {@code ResultSet} proxy which closes the given {@code Connection} and
+     * {@code Statement} if it is closed.
+     * 
+     * @param con the associated {@code Connection}
+     * @param stmt the associated {@code Statement}
+     * @param rs the {@code ResultSet} which backs the proxy
+     * @return a {@code ResultSet} proxy
+     */
+    public static final ResultSet newInstance(Connection con, Statement stmt, ResultSet rs) {
+        ResultSetWrapper proxy = new ResultSetWrapper(con, stmt, rs);
+        return (ResultSet) Proxy.newProxyInstance(rs.getClass().getClassLoader(),
+            new Class<?>[]{ResultSet.class}, proxy);
+    }
+
+    private ResultSetWrapper(Connection con, Statement stmt, ResultSet rs) {
+        connection = con;
+        statement = stmt;
+        resultSet = rs;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
+        if ("close".equals(m.getName())) {
+            DbUtility.close(connection, statement, resultSet);
+            return null;
+        } else {
+            return m.invoke(resultSet, args);
+        }
+    }
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/StreamWrapper.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/StreamWrapper.java
new file mode 100644
index 0000000..a81d60d
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/core/util/db/StreamWrapper.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.util.db;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.sql.SQLException;
+
+import org.apache.commons.io.input.CloseShieldInputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class StreamWrapper {
+
+    private final Logger log = LoggerFactory.getLogger(StreamWrapper.class);
+
+    private MarkDetectingInputStream stream;
+    private final long size;
+
+    /**
+     * Creates a wrapper for the given InputStream that can
+     * safely be passed as a parameter to the {@link ConnectionHelper#exec(String, Object...)},
+     * {@link ConnectionHelper#exec(String, Object[], boolean, int)} and
+     * {@link ConnectionHelper#update(String, Object[])} methods.
+     *
+     * @param in the InputStream to wrap
+     * @param size the size of the input stream
+     */
+    public StreamWrapper(InputStream in, long size) {
+        this.stream = new MarkDetectingInputStream(in);
+        this.size = size;
+    }
+    
+    public InputStream getStream() {
+        return new CloseShieldInputStream(stream);
+    }
+    
+    public long getSize() {
+        return size;
+    }
+
+    public void closeStream() {
+        try {
+            stream.close();
+        } catch (IOException e) {
+            log.error("Error while closing stream", e);
+        }
+    }
+
+    /**
+     * Resets the internal InputStream that it could be re-read.<br>
+     * Is used from {@link RetryManager} if a {@link SQLException} has occurred.<br>
+     * It relies on the assumption that the InputStream was not marked anywhere
+     * during reading.
+     *
+     * @return returns true if it was able to reset the Stream
+     */
+    public boolean resetStream() {
+        try {
+            if (!stream.isMarked()) {
+                stream.reset();
+                return true;
+            } else {
+                log.warn("Cannot reset stream to the beginning because it was marked.");
+                return false;
+            }
+        } catch (IOException e) {
+            return false;
+        }
+	}
+
+    private static class MarkDetectingInputStream extends FilterInputStream {
+
+        private boolean marked;
+
+        protected MarkDetectingInputStream(final InputStream in) {
+            super(in);
+        }
+
+        @Override
+        public synchronized void mark(final int readlimit) {
+            super.mark(readlimit);
+            marked = true;
+        }
+
+        private boolean isMarked() {
+            return marked;
+        }
+    }
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/data/core/InternalXAResource.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/data/core/InternalXAResource.java
new file mode 100644
index 0000000..c68d7d2
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/data/core/InternalXAResource.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.data.core;
+
+
+
+/**
+ * Interface implemented by resources that provide XA functionality.
+ */
+public interface InternalXAResource {
+
+    /**
+     * Associate this resource with a transaction. All further operations on
+     * the object should be interpreted as part of this transaction and changes
+     * recorded in some attribute of the transaction context.
+     * @param tx transaction context, if <code>null</code> disassociate
+     */
+    void associate(TransactionContext tx);
+
+    /**
+     * Invoked before one of the {@link #prepare}, {@link #commit} or
+     * {@link #rollback} method is called.
+     * @param tx transaction context
+     */
+    void beforeOperation(TransactionContext tx);
+
+    /**
+     * Prepare transaction. The transaction is identified by a transaction
+     * context.
+     * @param tx transaction context
+     * @throws TransactionException if an error occurs
+     */
+    void prepare(TransactionContext tx) throws TransactionException;
+
+    /**
+     * Commit transaction. The transaction is identified by a transaction
+     * context. If the method throws, other resources get their changes
+     * rolled back.
+     * @param tx transaction context
+     * @throws TransactionException if an error occurs
+     */
+    void commit(TransactionContext tx) throws TransactionException;
+
+    /**
+     * Rollback transaction. The transaction is identified by a transaction
+     * context.
+     * @param tx transaction context.
+     */
+    void rollback(TransactionContext tx) throws TransactionException;
+
+    /**
+     * Invoked after one of the {@link #prepare}, {@link #commit} or
+     * {@link #rollback} method has been called.
+     * @param tx transaction context
+     */
+    void afterOperation(TransactionContext tx);
+
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/data/core/TransactionContext.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/data/core/TransactionContext.java
new file mode 100644
index 0000000..e70b909
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/data/core/TransactionContext.java
@@ -0,0 +1,376 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.data.core;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.Xid;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Represents the transaction on behalf of the component that wants to
+ * explicitly demarcate transaction boundaries. After having been prepared,
+ * schedules a task that rolls back the transaction if some time passes without
+ * any further action. This will guarantee that global objects locked by one
+ * of the resources' {@link InternalXAResource#prepare} method, are eventually
+ * unlocked.
+ */
+public class TransactionContext {
+
+    /**
+     * Logger instance.
+     */
+    private static final Logger log = LoggerFactory.getLogger(TransactionContext.class);
+
+    private static final int STATUS_PREPARING = 1;
+    private static final int STATUS_PREPARED = 2;
+    private static final int STATUS_COMMITTING = 3;
+    private static final int STATUS_COMMITTED = 4;
+    private static final int STATUS_ROLLING_BACK = 5;
+    private static final int STATUS_ROLLED_BACK = 6;
+
+    /**
+     * The per thread associated Xid
+     */
+    private static final ThreadLocal<Xid> CURRENT_XID = new ThreadLocal<Xid>();
+
+    /**
+     * Transactional resources.
+     */
+    private final InternalXAResource[] resources;
+
+    /**
+    * The Xid
+    */
+   private final Xid xid;
+
+    /**
+     * Transaction attributes.
+     */
+    private final Map<String, Object> attributes = new HashMap<String, Object>();
+
+    /**
+     * Status.
+     */
+    private int status;
+
+    /**
+     * Flag indicating whether the association is currently suspended.
+     */
+    private boolean suspended;
+
+    /**
+     * Create a new instance of this class.
+     *
+     * @param xid associated xid
+     * @param resources transactional resources
+     */
+    public TransactionContext(Xid xid, InternalXAResource[] resources) {
+        this.xid = xid;
+        this.resources = resources;
+    }
+
+    /**
+     * Set an attribute on this transaction. If the value specified is
+     * <code>null</code>, it is semantically equivalent to
+     * {@link #removeAttribute}.
+     *
+     * @param name  attribute name
+     * @param value attribute value
+     */
+    public void setAttribute(String name, Object value) {
+        if (value == null) {
+            removeAttribute(name);
+        }
+        attributes.put(name, value);
+    }
+
+    /**
+     * Return an attribute value on this transaction.
+     *
+     * @param name attribute name
+     * @return attribute value, <code>null</code> if no attribute with that
+     *         name exists
+     */
+    public Object getAttribute(String name) {
+        return attributes.get(name);
+    }
+
+    /**
+     * Remove an attribute on this transaction.
+     *
+     * @param name attribute name
+     */
+    public void removeAttribute(String name) {
+        attributes.remove(name);
+    }
+
+    /**
+     * Prepare the transaction identified by this context. Prepares changes on
+     * all resources. If some resource reports an error on prepare,
+     * automatically rollback changes on all other resources. Throw exception
+     * at the end if errors were found.
+     *
+     * @throws XAException if an error occurs
+     */
+    public synchronized void prepare() throws XAException {
+        bindCurrentXid();
+        status = STATUS_PREPARING;
+        beforeOperation();
+
+        TransactionException txe = null;
+        for (int i = 0; i < resources.length; i++) {
+            try {
+                resources[i].prepare(this);
+            } catch (TransactionException e) {
+                txe = e;
+                break;
+            } catch (Exception e) {
+                txe = new TransactionException("Error while preparing resource " + resources, e);
+                break;
+            }
+        }
+
+        afterOperation();
+        status = STATUS_PREPARED;
+
+        if (txe != null) {
+            // force immediate rollback on error.
+            try {
+                rollback();
+            } catch (XAException e) {
+                /* ignore */
+            }
+            XAException e = new XAException(XAException.XA_RBOTHER);
+            e.initCause(txe);
+            throw e;
+        }
+    }
+
+    /**
+     * Commit the transaction identified by this context. Commits changes on
+     * all resources. If some resource reports an error on commit,
+     * automatically rollback changes on all other resources. Throw
+     * exception at the end if some commit failed.
+     *
+     * @throws XAException if an error occurs
+     */
+    public synchronized void commit() throws XAException {
+        if (status == STATUS_ROLLED_BACK) {
+            throw new XAException(XAException.XA_HEURRB);
+        }
+
+        boolean heuristicCommit = false;
+        bindCurrentXid();
+        status = STATUS_COMMITTING;
+        beforeOperation();
+
+        TransactionException txe = null;
+        for (int i = 0; i < resources.length; i++) {
+            InternalXAResource resource = resources[i];
+            if (txe != null) {
+                try {
+                    resource.rollback(this);
+                } catch (Exception e) {
+                    log.warn("Unable to rollback changes on " + resource, e);
+                }
+            } else {
+                try {
+                    resource.commit(this);
+                    heuristicCommit = true;
+                } catch (TransactionException e) {
+                    txe = e;
+                } catch (Exception e) {
+                    txe = new TransactionException("Error while committing resource " + resource, e);
+                }
+            }
+        }
+        afterOperation();
+        status = STATUS_COMMITTED;
+
+        cleanCurrentXid();
+
+        if (txe != null) {
+            XAException e = null;
+            if (heuristicCommit) {
+                e = new XAException(XAException.XA_HEURMIX);
+            } else {
+                e = new XAException(XAException.XA_HEURRB);
+            }
+            e.initCause(txe);
+            throw e;
+        }
+    }
+
+    /**
+     * Rollback the transaction identified by this context. Rolls back changes
+     * on all resources. Throws exception at the end if errors were found.
+     * @throws XAException if an error occurs
+     */
+    public synchronized void rollback() throws XAException {
+        if (status == STATUS_ROLLED_BACK) {
+            throw new XAException(XAException.XA_RBOTHER);
+        }
+        bindCurrentXid();
+        status = STATUS_ROLLING_BACK;
+        beforeOperation();
+
+        int errors = 0;
+        for (int i = 0; i < resources.length; i++) {
+            InternalXAResource resource = resources[i];
+            try {
+                resource.rollback(this);
+            } catch (Exception e) {
+                log.warn("Unable to rollback changes on " + resource, e);
+                errors++;
+            }
+        }
+        afterOperation();
+        status = STATUS_ROLLED_BACK;
+
+        cleanCurrentXid();
+
+        if (errors != 0) {
+            throw new XAException(XAException.XA_RBOTHER);
+        }
+    }
+
+    /**
+     * Invoke all of the registered resources' {@link InternalXAResource#beforeOperation}
+     * methods.
+     */
+    private void beforeOperation() {
+        for (int i = 0; i < resources.length; i++) {
+            resources[i].beforeOperation(this);
+        }
+    }
+
+    /**
+     * Invoke all of the registered resources' {@link InternalXAResource#afterOperation}
+     * methods.
+     */
+    private void afterOperation() {
+        for (int i = 0; i < resources.length; i++) {
+            resources[i].afterOperation(this);
+        }
+    }
+
+    /**
+     * Return a flag indicating whether the association is suspended.
+     *
+     * @return <code>true</code> if the association is suspended;
+     *         <code>false</code> otherwise
+     */
+    public boolean isSuspended() {
+        return suspended;
+    }
+
+    /**
+     * Set a flag indicating whether the association is suspended.
+     *
+     * @param suspended flag whether that the association is suspended.
+     */
+    public void setSuspended(boolean suspended) {
+        this.suspended = suspended;
+    }
+
+    /**
+     * Helper Method to bind the {@link Xid} associated with this {@link TransactionContext}
+     * to the {@link #CURRENT_XID} ThreadLocal.
+     */
+    private void bindCurrentXid() {
+        CURRENT_XID.set(xid);
+    }
+
+    /**
+     * Helper Method to clean the {@link Xid} associated with this {@link TransactionContext}
+     * from the {@link #CURRENT_XID} ThreadLocal.
+     */
+    private void cleanCurrentXid() {
+        CURRENT_XID.set(null);
+    }
+
+    /**
+     * Returns the {@link Xid} bind to the {@link #CURRENT_XID} ThreadLocal
+     * @return current Xid or null
+     */
+    private static Xid getCurrentXid() {
+        return CURRENT_XID.get();
+    }
+
+    /**
+     * Returns the current thread identifier. The identifier is either the
+     * current thread instance or the global transaction identifier wrapped 
+     * in a {@link XidWrapper}, when running under a transaction.
+     *
+     * @return current thread identifier
+     */
+    public static Object getCurrentThreadId() {
+        Xid xid = TransactionContext.getCurrentXid();
+        if (xid != null) {
+            return new XidWrapper(xid.getGlobalTransactionId());
+        } else {
+            return Thread.currentThread();
+        }
+    }
+
+    /**
+     * Compares the given thread identifiers for equality.
+     *
+     * @see #getCurrentThreadId()
+     */
+    public static boolean isSameThreadId(Object a, Object b) {
+        if (a == b) {
+            return true;
+        } else if (a != null) {
+        	return a.equals(b);
+        } else {
+            return false;
+        }
+    }
+    
+    /**
+     * Wrapper around a global transaction id (byte[]) 
+     * that handles hashCode and equals in a proper way.
+     */
+    private static class XidWrapper {
+    	private byte[] gtid;
+    	
+    	public XidWrapper(byte[] gtid) {
+    		this.gtid = gtid;
+    	}
+
+        @Override
+        public boolean equals(Object other) {
+            if (!(other instanceof XidWrapper)) {
+                return false;
+            }
+            return Arrays.equals((byte[]) gtid, ((XidWrapper)other).gtid);
+        }
+
+        @Override
+        public int hashCode() {
+            return Arrays.hashCode(gtid);
+        }
+    }
+
+}
diff --git a/jackrabbit-data/src/main/java/org/apache/jackrabbit/data/core/TransactionException.java b/jackrabbit-data/src/main/java/org/apache/jackrabbit/data/core/TransactionException.java
new file mode 100644
index 0000000..f2746f6
--- /dev/null
+++ b/jackrabbit-data/src/main/java/org/apache/jackrabbit/data/core/TransactionException.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.data.core;
+
+/**
+ * TransactionException is thrown when some operation inside the transaction
+ * fails.
+ */
+public class TransactionException extends Exception {
+
+    /**
+     * Creates an instance of this class. Takes a detail message as parameter.
+     *
+     * @param message message
+     */
+    public TransactionException(String message) {
+        super(message);
+    }
+
+    /**
+     * Creates an instance of this class. Takes a message and a root throwable
+     * as parameter.
+     *
+     * @param message   message
+     * @param rootCause root throwable
+     */
+    public TransactionException(String message, Throwable rootCause) {
+        super(message, rootCause);
+    }
+}
diff --git a/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/azure.properties b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/azure.properties
new file mode 100644
index 0000000..9a9a819
--- /dev/null
+++ b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/azure.properties
@@ -0,0 +1,17 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
+createTable=CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY, LENGTH BIGINT, LAST_MODIFIED BIGINT, DATA IMAGE)
diff --git a/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/db2.properties b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/db2.properties
new file mode 100644
index 0000000..f3f5615
--- /dev/null
+++ b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/db2.properties
@@ -0,0 +1,17 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+driver=COM.ibm.db2.jdbc.net.DB2Driver
+createTable=CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY NOT NULL, LENGTH BIGINT, LAST_MODIFIED BIGINT, DATA BLOB(1000M)) 
diff --git a/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/derby.properties b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/derby.properties
new file mode 100644
index 0000000..04d42dc
--- /dev/null
+++ b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/derby.properties
@@ -0,0 +1,17 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+# Tested with Apache Derby 10.3.1.4 on Windows XP (2007-12-11)
+driver=org.apache.derby.jdbc.EmbeddedDriver
\ No newline at end of file
diff --git a/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/h2.properties b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/h2.properties
new file mode 100644
index 0000000..bf9f1c9
--- /dev/null
+++ b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/h2.properties
@@ -0,0 +1,18 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+# Tested with H2 1.0.63 on Windows XP (2007-12-11)
+driver=org.h2.Driver
+storeStream=-1
\ No newline at end of file
diff --git a/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/ingres.properties b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/ingres.properties
new file mode 100644
index 0000000..0b08ca7
--- /dev/null
+++ b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/ingres.properties
@@ -0,0 +1,17 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+driver=com.ingres.jdbc.IngresDriver
+createTable=CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY NOT NULL, LENGTH BIGINT, LAST_MODIFIED BIGINT, DATA LONG BYTE) 
diff --git a/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/mssql.properties b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/mssql.properties
new file mode 100644
index 0000000..9a9a819
--- /dev/null
+++ b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/mssql.properties
@@ -0,0 +1,17 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
+createTable=CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY, LENGTH BIGINT, LAST_MODIFIED BIGINT, DATA IMAGE)
diff --git a/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/mysql.properties b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/mysql.properties
new file mode 100644
index 0000000..34654c6
--- /dev/null
+++ b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/mysql.properties
@@ -0,0 +1,19 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+# Tested with MySQL 5.0.27-community-nt on Windows XP (2007-12-11)
+# currently, the objects must fit in memory
+driver=com.mysql.jdbc.Driver
+createTable=CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY, LENGTH BIGINT, LAST_MODIFIED BIGINT, DATA BLOB(2147483647))
diff --git a/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/oracle.properties b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/oracle.properties
new file mode 100644
index 0000000..a0af792
--- /dev/null
+++ b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/oracle.properties
@@ -0,0 +1,18 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+# Tested with Oracle Database 10g Release 10.2.0.1.0 on Windows XP (2008-04-29)
+driver=oracle.jdbc.OracleDriver
+createTable=CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY, LENGTH NUMBER, LAST_MODIFIED NUMBER, DATA BLOB)
diff --git a/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/postgresql.properties b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/postgresql.properties
new file mode 100644
index 0000000..953adaa
--- /dev/null
+++ b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/postgresql.properties
@@ -0,0 +1,20 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+# Tested with PostgreSQL 8.2.4 on Windows XP (2007-12-11)
+# currently, the objects must fit in memory
+driver=org.postgresql.Driver
+table=datastore
+createTable=CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY, LENGTH BIGINT, LAST_MODIFIED BIGINT, DATA BYTEA)
diff --git a/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/sqlserver.properties b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/sqlserver.properties
new file mode 100644
index 0000000..11d0902
--- /dev/null
+++ b/jackrabbit-data/src/main/resources/org/apache/jackrabbit/core/data/db/sqlserver.properties
@@ -0,0 +1,18 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+# Tested with Microsoft SQL Server 2005 4 on Windows XP (2007-12-11)
+driver=com.microsoft.sqlserver.jdbc.SQLServerDriver
+createTable=CREATE TABLE ${tablePrefix}${table}(ID VARCHAR(255) PRIMARY KEY, LENGTH BIGINT, LAST_MODIFIED BIGINT, DATA IMAGE)
diff --git a/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/InMemoryBackend.java b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/InMemoryBackend.java
new file mode 100644
index 0000000..5360a74
--- /dev/null
+++ b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/InMemoryBackend.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.core.data;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * An in-memory backend implementation used to speed up testing.
+ */
+public class InMemoryBackend implements Backend {
+
+    private HashMap<DataIdentifier, byte[]> data = new HashMap<DataIdentifier, byte[]>();
+
+    private HashMap<DataIdentifier, Long> timeMap = new HashMap<DataIdentifier, Long>();
+    
+    private CachingDataStore store;
+    
+    private Properties properties;
+
+    @Override
+    public void init(CachingDataStore store, String homeDir, String config)
+            throws DataStoreException {
+        // ignore
+        log("init");
+        this.store = store;
+    }
+
+    @Override
+    public void close() {
+        // ignore
+        log("close");
+    }
+
+    @Override
+    public boolean exists(final DataIdentifier identifier) {
+        log("exists " + identifier);
+        return data.containsKey(identifier);
+    }
+
+    @Override
+    public Iterator<DataIdentifier> getAllIdentifiers()
+            throws DataStoreException {
+        log("getAllIdentifiers");
+        return data.keySet().iterator();
+    }
+
+    @Override
+    public InputStream read(final DataIdentifier identifier)
+            throws DataStoreException {
+        log("read " + identifier);
+        return new ByteArrayInputStream(data.get(identifier));
+    }
+
+    @Override
+    public void writeAsync(final DataIdentifier identifier, final File file,
+            final AsyncUploadCallback callback) throws DataStoreException {
+        this.write(identifier, file, true, callback);
+    }
+
+    @Override
+    public void write(final DataIdentifier identifier, final File file)
+            throws DataStoreException {
+        this.write(identifier, file, false, null);
+    }
+
+    @Override
+    public long getLastModified(final DataIdentifier identifier)
+            throws DataStoreException {
+        log("getLastModified " + identifier);
+        return timeMap.get(identifier);
+    }
+
+    @Override
+    public void deleteRecord(final DataIdentifier identifier)
+            throws DataStoreException {
+        timeMap.remove(identifier);
+        data.remove(identifier);
+    }
+
+    @Override
+    public Set<DataIdentifier> deleteAllOlderThan(final long min) throws DataStoreException {
+        log("deleteAllOlderThan " + min);
+        Set<DataIdentifier> tobeDeleted = new HashSet<DataIdentifier>();
+        for (Map.Entry<DataIdentifier, Long> entry : timeMap.entrySet()) {
+            DataIdentifier identifier = entry.getKey();
+            long timestamp = entry.getValue();
+            if (timestamp < min && !store.isInUse(identifier)
+                && store.confirmDelete(identifier)) {
+                store.deleteFromCache(identifier);
+                tobeDeleted.add(identifier);
+            }
+        }
+        for (DataIdentifier identifier : tobeDeleted) {
+            timeMap.remove(identifier);
+            data.remove(identifier);
+        }
+        return tobeDeleted;
+    }
+
+    @Override
+    public long getLength(final DataIdentifier identifier)
+            throws DataStoreException {
+        try {
+            return data.get(identifier).length;
+        } catch (Exception e) {
+            throw new DataStoreException(e);
+        }
+    }
+
+    @Override
+    public boolean exists(final DataIdentifier identifier, final boolean touch)
+            throws DataStoreException {
+        boolean retVal = data.containsKey(identifier);
+        if (retVal && touch) {
+            timeMap.put(identifier, System.currentTimeMillis());
+        }
+        return retVal;
+    }
+    
+    @Override
+    public void touch(DataIdentifier identifier, long minModifiedDate) {
+        timeMap.put(identifier, System.currentTimeMillis());
+    }
+
+    @Override
+    public void touchAsync(DataIdentifier identifier, long minModifiedDate,
+            AsyncTouchCallback callback) {
+        timeMap.put(identifier, System.currentTimeMillis());
+        callback.onSuccess(new AsyncTouchResult(identifier));
+    }
+
+    private void write(final DataIdentifier identifier, final File file,
+            final boolean async, final AsyncUploadCallback callback)
+            throws DataStoreException {
+        log("write " + identifier + " " + file.length());
+        byte[] buffer = new byte[(int) file.length()];
+        try {
+            if (async && callback == null) {
+                throw new IllegalArgumentException(
+                    "callback parameter cannot be null");
+            }
+            DataInputStream din = new DataInputStream(new FileInputStream(file));
+            din.readFully(buffer);
+            din.close();
+            data.put(identifier, buffer);
+            timeMap.put(identifier, System.currentTimeMillis());
+        } catch (IOException e) {
+            if (async) {
+                callback.onAbort(new AsyncUploadResult(identifier, file));
+            }
+            throw new DataStoreException(e);
+        }
+        if (async) {
+            callback.onSuccess(new AsyncUploadResult(identifier, file));
+        }
+    }
+    
+    /**
+     * Properties used to configure the backend. If provided explicitly before
+     * init is invoked then these take precedence
+     *
+     * @param properties to configure S3Backend
+     */
+    public void setProperties(Properties properties) {
+        this.properties = properties;
+    }
+
+    /**
+     * Log a message if logging is enabled.
+     * 
+     * @param message
+     *            the message
+     */
+    private void log(final String message) {
+        // System.out.println(message);
+    }
+}
diff --git a/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/InMemoryDataStore.java b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/InMemoryDataStore.java
new file mode 100644
index 0000000..b3c87e0
--- /dev/null
+++ b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/InMemoryDataStore.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.util.Properties;
+
+import org.apache.jackrabbit.core.data.Backend;
+import org.apache.jackrabbit.core.data.CachingDataStore;
+
+/**
+ * A caching data store that uses the in-memory backend.
+ */
+public class InMemoryDataStore extends CachingDataStore {
+    
+    private Properties properties;
+
+    @Override
+    protected Backend createBackend() {
+        InMemoryBackend backend = new InMemoryBackend();
+        if (properties != null) {
+            backend.setProperties(properties);
+        }
+        return backend;
+    }
+
+    @Override
+    protected String getMarkerFile() {
+        return "mem.init.done";
+    }
+    
+    /**
+     * Properties required to configure the S3Backend
+     */
+    public void setProperties(Properties properties) {
+        this.properties = properties;
+    }
+}
diff --git a/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/RandomInputStream.java b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/RandomInputStream.java
similarity index 100%
rename from jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/RandomInputStream.java
rename to jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/RandomInputStream.java
diff --git a/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestCachingFDS.java b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestCachingFDS.java
new file mode 100644
index 0000000..4c8ae6a
--- /dev/null
+++ b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestCachingFDS.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.core.data;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.Properties;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TestCachingFDS extends TestFileDataStore {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(TestCachingFDS.class);
+
+    private static final String PENDIND_UPLOAD_FILE = "async-pending-uploads.ser";
+
+    private static final String TO_BE_DELETED_UPLOAD_FILE = "async-tobedeleted-uploads.ser";
+
+    protected DataStore createDataStore() throws RepositoryException {
+        CachingFDS cacheFDS = new CachingFDS();
+        Properties props = loadProperties("/fs.properties");
+        String pathValue = props.getProperty(FSBackend.FS_BACKEND_PATH);
+        if (pathValue != null && !"".equals(pathValue.trim())) {
+            fsPath = pathValue + "/cachingFds" + "-"
+                + String.valueOf(randomGen.nextInt(100000)) + "-"
+                + String.valueOf(randomGen.nextInt(100000));
+        } else {
+            fsPath = dataStoreDir + "/cachingFds";
+        }
+        props.setProperty(FSBackend.FS_BACKEND_PATH, fsPath);
+        LOG.info("fsBackendPath [{}] set.", fsPath);
+        cacheFDS.setProperties(props);
+        cacheFDS.setSecret("12345");
+        cacheFDS.init(dataStoreDir);
+        return cacheFDS;
+    }
+
+    /**
+     * Test robustness of {@link AsyncUploadCache} corruption.
+     */
+    public void testAsyncUploadCacheCorruption() {
+        try {
+            ds = createDataStore();
+            File pendingUploads = new File(dataStoreDir + "/"
+                + PENDIND_UPLOAD_FILE);
+            FileOutputStream fos = new FileOutputStream(pendingUploads);
+            IOUtils.write("garbage-data", fos);
+            fos.close();
+
+            File tobeDeletedFile = new File(dataStoreDir + "/"
+                + TO_BE_DELETED_UPLOAD_FILE);
+            fos = new FileOutputStream(tobeDeletedFile);
+            IOUtils.write("garbage-data", fos);
+            fos.close();
+            ds.close();
+
+            doAddRecordTest();
+        } catch (Exception e) {
+            LOG.error("error:", e);
+            fail(e.getMessage());
+        }
+    }
+}
diff --git a/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestCachingFDSCacheOff.java b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestCachingFDSCacheOff.java
new file mode 100644
index 0000000..0980676
--- /dev/null
+++ b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestCachingFDSCacheOff.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.core.data;
+
+import java.util.Properties;
+
+import javax.jcr.RepositoryException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TestCachingFDSCacheOff extends TestFileDataStore {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(TestCachingFDS.class);
+    
+    protected DataStore createDataStore() throws RepositoryException {
+        CachingFDS cacheFDS = new CachingFDS();
+        Properties props = loadProperties("/fs.properties");
+        String pathValue = props.getProperty(FSBackend.FS_BACKEND_PATH);
+        if (pathValue != null && !"".equals(pathValue.trim())) {
+            fsPath = pathValue + "/cachingFds" + "-"
+                + String.valueOf(randomGen.nextInt(100000)) + "-"
+                + String.valueOf(randomGen.nextInt(100000));
+        } else {
+            fsPath = dataStoreDir + "/cachingFDS";
+        }
+        props.setProperty(FSBackend.FS_BACKEND_PATH, fsPath);
+        cacheFDS.setProperties(props);
+        cacheFDS.setSecret("12345");
+        cacheFDS.setCacheSize(0);
+        cacheFDS.init(dataStoreDir);
+        return cacheFDS;
+    }
+}
diff --git a/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestCaseBase.java b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestCaseBase.java
new file mode 100644
index 0000000..dad1e09
--- /dev/null
+++ b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestCaseBase.java
@@ -0,0 +1,682 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.SequenceInputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.Random;
+
+import javax.jcr.RepositoryException;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test base class which covers all scenarios.
+ */
+public abstract class TestCaseBase extends TestCase {
+
+    /**
+     * Logger
+     */
+    protected static final Logger LOG = LoggerFactory.getLogger(TestCaseBase.class);
+
+    /**
+     * temp directory
+     */
+    private static final String TEST_DIR = "target/temp";
+
+    /**
+     * Constant describing aws properties file path.
+     */
+    public static final String CONFIG = "config";
+
+    /**
+     * length of record to be added
+     */
+    protected int dataLength = 123456;
+
+    /**
+     * datastore directory path
+     */
+    protected String dataStoreDir;
+
+    protected DataStore ds;
+
+    /**
+     * Random number generator to populate data
+     */
+    protected Random randomGen = new Random();
+
+    /**
+     * Delete temporary directory.
+     */
+    @Override
+    protected void setUp() throws Exception {
+        dataStoreDir = TEST_DIR + "-" + String.valueOf(randomGen.nextInt(9999))
+            + "-" + String.valueOf(randomGen.nextInt(9999));
+        // delete directory if it exists
+        File directory = new File(dataStoreDir);
+        if (directory.exists()) {
+            boolean delSuccessFul = FileUtils.deleteQuietly(directory);
+            int retry = 2, count = 0;
+            while (!delSuccessFul && count <= retry) {
+                // try once more
+                delSuccessFul = FileUtils.deleteQuietly(new File(dataStoreDir));
+                count++;
+            }
+            LOG.info("setup : directory [" + dataStoreDir + "] deleted ["
+                + delSuccessFul + "]");
+        }
+    }
+
+    @Override
+    protected void tearDown() {
+        boolean delSuccessFul = FileUtils.deleteQuietly(new File(dataStoreDir));
+        int retry = 2, count = 0;
+        while (!delSuccessFul && count <= retry) {
+            // try once more
+            delSuccessFul = FileUtils.deleteQuietly(new File(dataStoreDir));
+            count++;
+        }
+        LOG.info("tearDown : directory [" + dataStoreDir + "] deleted ["
+            + delSuccessFul + "]");
+    }
+    /**
+     * Testcase to validate {@link DataStore#addRecord(InputStream)} API.
+     */
+    public void testAddRecord() {
+        try {
+            long start = System.currentTimeMillis();
+            LOG.info("Testcase: " + this.getClass().getName()
+                + "#addRecord, testDir=" + dataStoreDir);
+            doAddRecordTest();
+            LOG.info("Testcase: " + this.getClass().getName()
+                + "#addRecord finished, time taken = ["
+                + (System.currentTimeMillis() - start) + "]ms");
+        } catch (Exception e) {
+            LOG.error("error:", e);
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * Testcase to validate {@link DataStore#getRecord(DataIdentifier)} API.
+     */
+    public void testGetRecord() {
+        try {
+            long start = System.currentTimeMillis();
+            LOG.info("Testcase: " + this.getClass().getName()
+                + "#testGetRecord, testDir=" + dataStoreDir);
+            doGetRecordTest();
+            LOG.info("Testcase: " + this.getClass().getName()
+                + "#testGetRecord finished, time taken = ["
+                + (System.currentTimeMillis() - start) + "]ms");
+        } catch (Exception e) {
+            LOG.error("error:", e);
+        }
+    }
+    
+    /**
+     * Testcase to validate {@link DataStore#getAllIdentifiers()} API.
+     */
+    public void testGetAllIdentifiers() {
+        try {
+            long start = System.currentTimeMillis();
+            LOG.info("Testcase: " + this.getClass().getName()
+                + "#testGetAllIdentifiers, testDir=" + dataStoreDir);
+            doGetAllIdentifiersTest();
+            LOG.info("Testcase: " + this.getClass().getName()
+                + "#testGetAllIdentifiers finished, time taken = ["
+                + (System.currentTimeMillis() - start) + "]ms");
+        } catch (Exception e) {
+            LOG.error("error:", e);
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * Testcase to validate {@link DataStore#updateModifiedDateOnAccess(long)}
+     * API.
+     */
+    public void testUpdateLastModifiedOnAccess() {
+        try {
+            long start = System.currentTimeMillis();
+            LOG.info("Testcase: " + this.getClass().getName()
+                + "#testUpdateLastModifiedOnAccess, testDir=" + dataStoreDir);
+            doUpdateLastModifiedOnAccessTest();
+            LOG.info("Testcase: " + this.getClass().getName()
+                + "#testUpdateLastModifiedOnAccess finished, time taken = ["
+                + (System.currentTimeMillis() - start) + "]ms");
+        } catch (Exception e) {
+            LOG.error("error:", e);
+        }
+    }
+
+    /**
+     * Testcase to validate
+     * {@link MultiDataStoreAware#deleteRecord(DataIdentifier)}.API.
+     */
+    public void testDeleteRecord() {
+        try {
+            long start = System.currentTimeMillis();
+            LOG.info("Testcase: " + this.getClass().getName()
+                + "#testDeleteRecord, testDir=" + dataStoreDir);
+            doDeleteRecordTest();
+            LOG.info("Testcase: " + this.getClass().getName()
+                + "#testDeleteRecord finished, time taken = ["
+                + (System.currentTimeMillis() - start) + "]ms");
+        } catch (Exception e) {
+            LOG.error("error:", e);
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * Testcase to validate {@link DataStore#deleteAllOlderThan(long)} API.
+     */
+    public void testDeleteAllOlderThan() {
+        try {
+            long start = System.currentTimeMillis();
+            LOG.info("Testcase: " + this.getClass().getName()
+                + "#testDeleteAllOlderThan, testDir=" + dataStoreDir);
+            doDeleteAllOlderThan();
+            LOG.info("Testcase: " + this.getClass().getName()
+                + "#testDeleteAllOlderThan finished, time taken = ["
+                + (System.currentTimeMillis() - start) + "]ms");
+        } catch (Exception e) {
+            LOG.error("error:", e);
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * Testcase to validate {@link DataStore#getRecordFromReference(String)}
+     */
+    public void testReference() {
+        try {
+            long start = System.currentTimeMillis();
+            LOG.info("Testcase: " + this.getClass().getName()
+                + "#testReference, testDir=" + dataStoreDir);
+            doReferenceTest();
+            LOG.info("Testcase: " + this.getClass().getName()
+                + "#testReference finished, time taken = ["
+                + (System.currentTimeMillis() - start) + "]ms");
+        } catch (Exception e) {
+            LOG.error("error:", e);
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * Testcase to validate mixed scenario use of {@link DataStore}.
+     */
+    public void testSingleThread() {
+        try {
+            long start = System.currentTimeMillis();
+            LOG.info("Testcase: " + this.getClass().getName()
+                + "#testSingleThread, testDir=" + dataStoreDir);
+            doTestSingleThread();
+            LOG.info("Testcase: " + this.getClass().getName()
+                + "#testSingleThread finished, time taken = ["
+                + (System.currentTimeMillis() - start) + "]ms");
+        } catch (Exception e) {
+            LOG.error("error:", e);
+            fail(e.getMessage());
+        }
+    }
+
+    /**
+     * Testcase to validate mixed scenario use of {@link DataStore} in
+     * multi-threaded concurrent environment.
+     */
+    public void testMultiThreaded() {
+        try {
+            long start = System.currentTimeMillis();
+            LOG.info("Testcase: " + this.getClass().getName()
+                + "#testMultiThreaded, testDir=" + dataStoreDir);
+            doTestMultiThreaded();
+            LOG.info("Testcase: " + this.getClass().getName()
+                + "#testMultiThreaded finished, time taken = ["
+                + (System.currentTimeMillis() - start) + "]ms");
+        } catch (Exception e) {
+            LOG.error("error:", e);
+            fail(e.getMessage());
+        }
+
+    }
+
+    protected abstract DataStore createDataStore() throws RepositoryException ;
+
+    /**
+     * Test {@link DataStore#addRecord(InputStream)} and assert length of added
+     * record.
+     */
+    protected void doAddRecordTest() throws Exception {
+        ds = createDataStore();
+        byte[] data = new byte[dataLength];
+        randomGen.nextBytes(data);
+        DataRecord rec = ds.addRecord(new ByteArrayInputStream(data));
+        assertEquals(data.length, rec.getLength());
+        assertRecord(data, rec);
+        ds.close();
+    }
+
+    /**
+     * Test {@link DataStore#getRecord(DataIdentifier)} and assert length and
+     * inputstream.
+     */
+    protected void doGetRecordTest() throws Exception {
+        ds = createDataStore();
+        byte[] data = new byte[dataLength];
+        randomGen.nextBytes(data);
+        DataRecord rec = ds.addRecord(new ByteArrayInputStream(data));
+        rec = ds.getRecord(rec.getIdentifier());
+        assertEquals(data.length, rec.getLength());
+        assertRecord(data, rec);
+        ds.close();
+    }
+
+    /**
+     * Test {@link MultiDataStoreAware#deleteRecord(DataIdentifier)}.
+     */
+    protected void doDeleteRecordTest() throws Exception {
+        ds = createDataStore();
+        Random random = randomGen;
+        byte[] data1 = new byte[dataLength];
+        random.nextBytes(data1);
+        DataRecord rec1 = ds.addRecord(new ByteArrayInputStream(data1));
+
+        byte[] data2 = new byte[dataLength];
+        random.nextBytes(data2);
+        DataRecord rec2 = ds.addRecord(new ByteArrayInputStream(data2));
+
+        byte[] data3 = new byte[dataLength];
+        random.nextBytes(data3);
+        DataRecord rec3 = ds.addRecord(new ByteArrayInputStream(data3));
+
+        ((MultiDataStoreAware)ds).deleteRecord(rec2.getIdentifier());
+
+        assertNull("rec2 should be null",
+            ds.getRecordIfStored(rec2.getIdentifier()));
+        assertEquals(new ByteArrayInputStream(data1),
+            ds.getRecord(rec1.getIdentifier()).getStream());
+        assertEquals(new ByteArrayInputStream(data3),
+            ds.getRecord(rec3.getIdentifier()).getStream());
+        ds.close();
+    }
+
+    /**
+     * Test {@link DataStore#getAllIdentifiers()} and asserts all identifiers
+     * are returned.
+     */
+    protected void doGetAllIdentifiersTest() throws Exception {
+        ds = createDataStore();
+        List<DataIdentifier> list = new ArrayList<DataIdentifier>();
+        Random random = randomGen;
+        byte[] data = new byte[dataLength];
+        random.nextBytes(data);
+        DataRecord rec = ds.addRecord(new ByteArrayInputStream(data));
+        list.add(rec.getIdentifier());
+
+        data = new byte[dataLength];
+        random.nextBytes(data);
+        rec = ds.addRecord(new ByteArrayInputStream(data));
+        list.add(rec.getIdentifier());
+
+        data = new byte[dataLength];
+        random.nextBytes(data);
+        rec = ds.addRecord(new ByteArrayInputStream(data));
+        list.add(rec.getIdentifier());
+
+        Iterator<DataIdentifier> itr = ds.getAllIdentifiers();
+        while (itr.hasNext()) {
+            assertTrue("record found on list", list.remove(itr.next()));
+        }
+        assertEquals(0, list.size());
+        ds.close();
+    }
+
+    /**
+     * Asserts that timestamp of all records accessed after
+     * {@link DataStore#updateModifiedDateOnAccess(long)} invocation.
+     */
+    protected void doUpdateLastModifiedOnAccessTest() throws Exception {
+        ds = createDataStore();
+        Random random = randomGen;
+        byte[] data = new byte[dataLength];
+        random.nextBytes(data);
+        DataRecord rec1 = ds.addRecord(new ByteArrayInputStream(data));
+
+        data = new byte[dataLength];
+        random.nextBytes(data);
+        DataRecord rec2 = ds.addRecord(new ByteArrayInputStream(data));
+        LOG.debug("rec2 timestamp=" + rec2.getLastModified());
+
+        // sleep for some time to ensure that async upload completes in backend.
+        sleep(6000);
+        long updateTime = System.currentTimeMillis();
+        LOG.debug("updateTime=" + updateTime);
+        ds.updateModifiedDateOnAccess(updateTime);
+
+        // sleep to workaround System.currentTimeMillis granularity.
+        sleep(3000);
+        data = new byte[dataLength];
+        random.nextBytes(data);
+        DataRecord rec3 = ds.addRecord(new ByteArrayInputStream(data));
+
+        data = new byte[dataLength];
+        random.nextBytes(data);
+        DataRecord rec4 = ds.addRecord(new ByteArrayInputStream(data));
+
+        rec1 = ds.getRecord(rec1.getIdentifier());
+
+        assertEquals("rec1 touched", true, rec1.getLastModified() > updateTime);
+        LOG.debug("rec2 timestamp=" + rec2.getLastModified());
+        assertEquals("rec2 not touched", true,
+            rec2.getLastModified() < updateTime);
+        assertEquals("rec3 touched", true, rec3.getLastModified() > updateTime);
+        assertEquals("rec4 touched", true, rec4.getLastModified() > updateTime);
+        ds.close();
+
+    }
+
+    /**
+     * Asserts that {@link DataStore#deleteAllOlderThan(long)} only deleted
+     * records older than argument passed.
+     */
+    protected void doDeleteAllOlderThan() throws Exception {
+        ds = createDataStore();
+        Random random = randomGen;
+        byte[] data = new byte[dataLength];
+        random.nextBytes(data);
+        DataRecord rec1 = ds.addRecord(new ByteArrayInputStream(data));
+
+        data = new byte[dataLength];
+        random.nextBytes(data);
+        DataRecord rec2 = ds.addRecord(new ByteArrayInputStream(data));
+
+        // sleep for some time to ensure that async upload completes in backend.
+        sleep(10000);
+        long updateTime = System.currentTimeMillis();
+        ds.updateModifiedDateOnAccess(updateTime);
+        
+        // sleep to workaround System.currentTimeMillis granularity.
+        sleep(3000);
+        data = new byte[dataLength];
+        random.nextBytes(data);
+        DataRecord rec3 = ds.addRecord(new ByteArrayInputStream(data));
+
+        data = new byte[dataLength];
+        random.nextBytes(data);
+        DataRecord rec4 = ds.addRecord(new ByteArrayInputStream(data));
+
+        rec1 = ds.getRecord(rec1.getIdentifier());
+        ds.clearInUse();
+        assertEquals("only rec2 should be deleted", 1,
+            ds.deleteAllOlderThan(updateTime));
+        assertNull("rec2 should be null",
+            ds.getRecordIfStored(rec2.getIdentifier()));
+
+        Iterator<DataIdentifier> itr = ds.getAllIdentifiers();
+        List<DataIdentifier> list = new ArrayList<DataIdentifier>();
+        list.add(rec1.getIdentifier());
+        list.add(rec3.getIdentifier());
+        list.add(rec4.getIdentifier());
+        while (itr.hasNext()) {
+            assertTrue("record found on list", list.remove(itr.next()));
+        }
+
+        assertEquals("touched records found", 0, list.size());
+        assertEquals("rec1 touched", true, rec1.getLastModified() > updateTime);
+        assertEquals("rec3 touched", true, rec3.getLastModified() > updateTime);
+        assertEquals("rec4 touched", true, rec4.getLastModified() > updateTime);
+        ds.close();
+    }
+
+    /**
+     * Test if record can be accessed via
+     * {@link DataStore#getRecordFromReference(String)}
+     */
+    protected void doReferenceTest() throws Exception {
+        ds = createDataStore();
+        byte[] data = new byte[dataLength];
+        randomGen.nextBytes(data);
+        String reference;
+        DataRecord record = ds.addRecord(new ByteArrayInputStream(data));
+        reference = record.getReference();
+        assertReference(data, reference, ds);
+        ds.close();
+    }
+
+    /**
+     * Method to validate mixed scenario use of {@link DataStore}.
+     */
+    protected void doTestSingleThread() throws Exception {
+        ds = createDataStore();
+        doTestMultiThreaded(ds, 1);
+        ds.close();
+    }
+
+    /**
+     * Method to validate mixed scenario use of {@link DataStore} in
+     * multi-threaded concurrent environment.
+     */
+    protected void doTestMultiThreaded() throws Exception {
+        ds = createDataStore();
+        doTestMultiThreaded(ds, 4);
+        ds.close();
+    }
+
+    /**
+     * Method to assert record with byte array.
+     */
+    protected void assertRecord(byte[] expected, DataRecord record)
+            throws DataStoreException, IOException {
+        InputStream stream = record.getStream();
+        try {
+            for (int i = 0; i < expected.length; i++) {
+                assertEquals(expected[i] & 0xff, stream.read());
+            }
+            assertEquals(-1, stream.read());
+        } finally {
+            stream.close();
+        }
+    }
+
+    /**
+     * Method to run {@link TestCaseBase#doTest(DataStore, int)} in multiple
+     * concurrent threads.
+     */
+    protected void doTestMultiThreaded(final DataStore ds, int threadCount)
+            throws Exception {
+        final Exception[] exception = new Exception[1];
+        Thread[] threads = new Thread[threadCount];
+        for (int i = 0; i < threadCount; i++) {
+            final int x = i;
+            Thread t = new Thread() {
+                public void run() {
+                    try {
+                        doTest(ds, x);
+                    } catch (Exception e) {
+                        exception[0] = e;
+                    }
+                }
+            };
+            threads[i] = t;
+            t.start();
+        }
+        for (int i = 0; i < threadCount; i++) {
+            threads[i].join();
+        }
+        if (exception[0] != null) {
+            throw exception[0];
+        }
+    }
+
+    /**
+     * Assert randomly read stream from record.
+     */
+    void doTest(DataStore ds, int offset) throws Exception {
+        ArrayList<DataRecord> list = new ArrayList<DataRecord>();
+        HashMap<DataRecord, Integer> map = new HashMap<DataRecord, Integer>();
+        for (int i = 0; i < 10; i++) {
+            int size = 100000 - (i * 100);
+            RandomInputStream in = new RandomInputStream(size + offset, size);
+            DataRecord rec = ds.addRecord(in);
+            list.add(rec);
+            map.put(rec, new Integer(size));
+        }
+        Random random = new Random(1);
+        for (int i = 0; i < list.size(); i++) {
+            int pos = random.nextInt(list.size());
+            DataRecord rec = list.get(pos);
+            int size = map.get(rec);
+            rec = ds.getRecord(rec.getIdentifier());
+            assertEquals(size, rec.getLength());
+            RandomInputStream expected = new RandomInputStream(size + offset,
+                size);
+            InputStream in = rec.getStream();
+            // Workaround for race condition that can happen with low cache size relative to the test
+            // read immediately
+            byte[] buffer = new byte[1];
+            in.read(buffer);
+            in = new SequenceInputStream(new ByteArrayInputStream(buffer), in);
+
+            if (random.nextBoolean()) {
+                in = readInputStreamRandomly(in, random);
+            }
+            assertEquals(expected, in);
+        }
+    }
+
+    InputStream readInputStreamRandomly(InputStream in, Random random)
+            throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        byte[] buffer = new byte[8000];
+        while (true) {
+            if (random.nextBoolean()) {
+                int x = in.read();
+                if (x < 0) {
+                    break;
+                }
+                out.write(x);
+            } else {
+                if (random.nextBoolean()) {
+                    int l = in.read(buffer);
+                    if (l < 0) {
+                        break;
+                    }
+                    out.write(buffer, 0, l);
+                } else {
+                    int offset = random.nextInt(buffer.length / 2);
+                    int len = random.nextInt(buffer.length / 2);
+                    int l = in.read(buffer, offset, len);
+                    if (l < 0) {
+                        break;
+                    }
+                    out.write(buffer, offset, l);
+                }
+            }
+        }
+        in.close();
+        return new ByteArrayInputStream(out.toByteArray());
+    }
+
+    /**
+     * Assert two inputstream
+     */
+    protected void assertEquals(InputStream a, InputStream b)
+            throws IOException {
+        try {
+            assertTrue("binary not equal",
+                org.apache.commons.io.IOUtils.contentEquals(a, b));
+        } finally {
+            try {
+                a.close();
+            } catch (Exception ignore) {
+            }
+            try {
+                b.close();
+            } catch (Exception ignore) {
+            }
+        }
+    }
+
+    /**
+     * Assert inputstream read from reference.
+     */
+    protected void assertReference(byte[] expected, String reference,
+            DataStore store) throws Exception {
+        DataRecord record = store.getRecordFromReference(reference);
+        assertNotNull(record);
+        assertEquals(expected.length, record.getLength());
+
+        InputStream stream = record.getStream();
+        try {
+            assertTrue("binary not equal",
+                org.apache.commons.io.IOUtils.contentEquals(
+                    new ByteArrayInputStream(expected), stream));
+        } finally {
+            stream.close();
+        }
+    }
+    
+    /**
+     * Utility method to stop execution for duration time.
+     * 
+     * @param duration
+     *            time in milli seconds
+     */
+    protected void sleep(long duration) {
+        long expected = System.currentTimeMillis() + duration;
+        while (System.currentTimeMillis() < expected) {
+            try {
+                Thread.sleep(1);
+            } catch (InterruptedException ie) {
+
+            }
+        }
+    }
+    
+    /**
+     * Return {@link Properties} from class resource. Return empty
+     * {@link Properties} if not found.
+     */
+    protected Properties loadProperties(String resource) {
+        Properties configProp = new Properties();
+        try {
+            configProp.load(this.getClass().getResourceAsStream(resource));
+        } catch (Exception ignore) {
+
+        }
+        return configProp;
+    }
+}
diff --git a/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestFileDataStore.java b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestFileDataStore.java
new file mode 100644
index 0000000..facc025
--- /dev/null
+++ b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestFileDataStore.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jackrabbit.core.data;
+
+import java.io.File;
+import java.util.Properties;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test cases to test {@link FileDataStore}
+ */
+public class TestFileDataStore extends TestCaseBase {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(TestFileDataStore.class);
+
+    String fsPath;
+
+    @Override
+    protected DataStore createDataStore() throws RepositoryException {
+        FileDataStore fds = new FileDataStore();
+        Properties props = loadProperties("/fs.properties");
+        String pathValue = props.getProperty(FSBackend.FS_BACKEND_PATH);
+        if (pathValue != null && !"".equals(pathValue.trim())) {
+            fsPath = pathValue + "/fds" + "-"
+                + String.valueOf(randomGen.nextInt(100000)) + "-"
+                + String.valueOf(randomGen.nextInt(100000));
+        } else {
+            fsPath = dataStoreDir + "/repository/datastore";
+        }
+        LOG.info("path [{}] set.", fsPath);
+        fds.setPath(fsPath);
+        fds.init(dataStoreDir);
+        return fds;
+    }
+
+    @Override
+    protected void tearDown() {
+        LOG.info("cleaning fsPath [{}]", fsPath);
+        File f = new File(fsPath);
+        try {
+            for (int i = 0; i < 4 && f.exists(); i++) {
+                FileUtils.deleteQuietly(f);
+                Thread.sleep(2000);
+            }
+        } catch (Exception ignore) {
+
+        }
+        super.tearDown();
+    }
+}
diff --git a/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestInMemDs.java b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestInMemDs.java
new file mode 100644
index 0000000..d63b2b6
--- /dev/null
+++ b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestInMemDs.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import javax.jcr.RepositoryException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test {@link CachingDataStore} with InMemoryBackend and local cache on.
+ */
+public class TestInMemDs extends TestCaseBase {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(TestInMemDs.class);
+
+    @Override
+    protected DataStore createDataStore() throws RepositoryException {
+        InMemoryDataStore inMemDS = new InMemoryDataStore();
+        inMemDS.setProperties(null);
+        inMemDS.init(dataStoreDir);
+        inMemDS.setSecret("12345");
+        return inMemDS;
+    }
+    
+
+}
diff --git a/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestInMemDsCacheOff.java b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestInMemDsCacheOff.java
new file mode 100644
index 0000000..1b5f956
--- /dev/null
+++ b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestInMemDsCacheOff.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import javax.jcr.RepositoryException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test {@link CachingDataStore} with InMemoryBackend and local cache off.
+ */
+
+public class TestInMemDsCacheOff extends TestCaseBase {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(TestInMemDsCacheOff.class);
+    @Override
+    protected DataStore createDataStore() throws RepositoryException {
+        InMemoryDataStore inMemDS = new InMemoryDataStore();
+        inMemDS.setProperties(null);
+        inMemDS.init(dataStoreDir);
+        inMemDS.setSecret("12345");
+        inMemDS.setCacheSize(0);
+        return inMemDS;
+    }
+}
diff --git a/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestLocalCache.java b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestLocalCache.java
new file mode 100644
index 0000000..3c52a8d
--- /dev/null
+++ b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/TestLocalCache.java
@@ -0,0 +1,402 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.core.data.util.NamedThreadFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Testcase to test local cache.
+ */
+public class TestLocalCache extends TestCase {
+
+    private static final String CACHE_DIR = "target/cache";
+
+    private static final String TEMP_DIR = "target/temp";
+
+    private static final String TARGET_DIR = "target";
+
+    protected String cacheDirPath;
+
+    protected String tempDirPath;
+
+    /**
+     * Random number generator to populate data
+     */
+    protected Random randomGen = new Random();
+
+    private static final Logger LOG = LoggerFactory.getLogger(TestLocalCache.class);
+
+    @Override
+    protected void setUp() {
+        try {
+            cacheDirPath = CACHE_DIR + "-"
+                + String.valueOf(randomGen.nextInt(9999)) + "-"
+                + String.valueOf(randomGen.nextInt(9999));
+            File cachedir = new File(cacheDirPath);
+            for (int i = 0; i < 4 && cachedir.exists(); i++) {
+                FileUtils.deleteQuietly(cachedir);
+                Thread.sleep(1000);
+            }
+            cachedir.mkdirs();
+
+            tempDirPath = TEMP_DIR + "-"
+                + String.valueOf(randomGen.nextInt(9999)) + "-"
+                + String.valueOf(randomGen.nextInt(9999));
+            File tempdir = new File(tempDirPath);
+            for (int i = 0; i < 4 && tempdir.exists(); i++) {
+                FileUtils.deleteQuietly(tempdir);
+                Thread.sleep(1000);
+            }
+            tempdir.mkdirs();
+        } catch (Exception e) {
+            LOG.error("error:", e);
+            fail();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        File cachedir = new File(cacheDirPath);
+        for (int i = 0; i < 4 && cachedir.exists(); i++) {
+            FileUtils.deleteQuietly(cachedir);
+            Thread.sleep(1000);
+        }
+
+        File tempdir = new File(tempDirPath);
+        for (int i = 0; i < 4 && tempdir.exists(); i++) {
+            FileUtils.deleteQuietly(tempdir);
+            Thread.sleep(1000);
+        }
+    }
+
+    /**
+     * Test to validate store retrieve in cache.
+     */
+    public void testStoreRetrieve() {
+        try {
+            AsyncUploadCache pendingFiles = new AsyncUploadCache();
+            pendingFiles.init(tempDirPath, cacheDirPath, 100);
+            pendingFiles.reset();
+            LocalCache cache = new LocalCache(cacheDirPath, tempDirPath, 400,
+                0.95, 0.70, pendingFiles);
+            Random random = new Random(12345);
+            byte[] data = new byte[100];
+            Map<String, byte[]> byteMap = new HashMap<String, byte[]>();
+            random.nextBytes(data);
+            byteMap.put("a1", data);
+
+            data = new byte[100];
+            random.nextBytes(data);
+            byteMap.put("a2", data);
+
+            data = new byte[100];
+            random.nextBytes(data);
+            byteMap.put("a3", data);
+
+            cache.store("a1", new ByteArrayInputStream(byteMap.get("a1")));
+            cache.store("a2", new ByteArrayInputStream(byteMap.get("a2")));
+            cache.store("a3", new ByteArrayInputStream(byteMap.get("a3")));
+            InputStream result = cache.getIfStored("a1");
+            assertEquals(new ByteArrayInputStream(byteMap.get("a1")), result);
+            IOUtils.closeQuietly(result);
+            result = cache.getIfStored("a2");
+            assertEquals(new ByteArrayInputStream(byteMap.get("a2")), result);
+            IOUtils.closeQuietly(result);
+            result = cache.getIfStored("a3");
+            assertEquals(new ByteArrayInputStream(byteMap.get("a3")), result);
+            IOUtils.closeQuietly(result);
+        } catch (Exception e) {
+            LOG.error("error:", e);
+            fail();
+        }
+    }
+
+    /**
+     * Test to verify cache's purging if cache current size exceeds
+     * cachePurgeTrigFactor * size.
+     */
+    public void testAutoPurge() {
+        try {
+            AsyncUploadCache pendingFiles = new AsyncUploadCache();
+            pendingFiles.init(tempDirPath, cacheDirPath, 100);
+            pendingFiles.reset();
+            LocalCache cache = new LocalCache(cacheDirPath, tempDirPath, 400,
+                0.95, 0.70, pendingFiles);
+            Random random = new Random(12345);
+            byte[] data = new byte[100];
+            Map<String, byte[]> byteMap = new HashMap<String, byte[]>();
+            random.nextBytes(data);
+            byteMap.put("a1", data);
+
+            data = new byte[100];
+            random.nextBytes(data);
+            byteMap.put("a2", data);
+
+            data = new byte[100];
+            random.nextBytes(data);
+            byteMap.put("a3", data);
+
+            data = new byte[100];
+            random.nextBytes(data);
+            byteMap.put("a4", data);
+
+            cache.store("a1", new ByteArrayInputStream(byteMap.get("a1")));
+            cache.store("a2", new ByteArrayInputStream(byteMap.get("a2")));
+            cache.store("a3", new ByteArrayInputStream(byteMap.get("a3")));
+
+            InputStream result = cache.getIfStored("a1");
+            assertEquals(new ByteArrayInputStream(byteMap.get("a1")), result);
+            IOUtils.closeQuietly(result);
+            result = cache.getIfStored("a2");
+            assertEquals(new ByteArrayInputStream(byteMap.get("a2")), result);
+            IOUtils.closeQuietly(result);
+            result = cache.getIfStored("a3");
+            assertEquals(new ByteArrayInputStream(byteMap.get("a3")), result);
+            IOUtils.closeQuietly(result);
+
+            data = new byte[90];
+            random.nextBytes(data);
+            byteMap.put("a4", data);
+            // storing a4 should purge cache
+            cache.store("a4", new ByteArrayInputStream(byteMap.get("a4")));
+            Thread.sleep(1000);
+
+            result = cache.getIfStored("a1");
+            assertNull("a1 should be null", result);
+            IOUtils.closeQuietly(result);
+
+            result = cache.getIfStored("a2");
+            assertNull("a2 should be null", result);
+            IOUtils.closeQuietly(result);
+
+            result = cache.getIfStored("a3");
+            assertEquals(new ByteArrayInputStream(byteMap.get("a3")), result);
+            IOUtils.closeQuietly(result);
+
+            result = cache.getIfStored("a4");
+            assertEquals(new ByteArrayInputStream(byteMap.get("a4")), result);
+            IOUtils.closeQuietly(result);
+
+            data = new byte[100];
+            random.nextBytes(data);
+            byteMap.put("a5", data);
+            cache.store("a5", new ByteArrayInputStream(byteMap.get("a5")));
+            result = cache.getIfStored("a3");
+            assertEquals(new ByteArrayInputStream(byteMap.get("a3")), result);
+            IOUtils.closeQuietly(result);
+        } catch (Exception e) {
+            LOG.error("error:", e);
+            fail();
+        }
+    }
+
+    /**
+     * Test to verify cache's purging if cache current size exceeds
+     * cachePurgeTrigFactor * size.
+     */
+    public void testAutoPurgeWithPendingUpload() {
+        try {
+            AsyncUploadCache pendingFiles = new AsyncUploadCache();
+            pendingFiles.init(tempDirPath, cacheDirPath, 100);
+            pendingFiles.reset();
+            LocalCache cache = new LocalCache(cacheDirPath, tempDirPath, 400,
+                0.95, 0.70, pendingFiles);
+            Random random = new Random(12345);
+            byte[] data = new byte[125];
+            Map<String, byte[]> byteMap = new HashMap<String, byte[]>();
+            random.nextBytes(data);
+            byteMap.put("a1", data);
+
+            data = new byte[125];
+            random.nextBytes(data);
+            byteMap.put("a2", data);
+
+            data = new byte[125];
+            random.nextBytes(data);
+            byteMap.put("a3", data);
+
+            data = new byte[100];
+            random.nextBytes(data);
+            byteMap.put("a4", data);
+            File tempDir = new File(tempDirPath);
+            File f = File.createTempFile("test", "tmp", tempDir);
+            FileOutputStream fos = new FileOutputStream(f);
+            fos.write(byteMap.get("a1"));
+            fos.close();
+            AsyncUploadCacheResult result = cache.store("a1", f, true);
+            assertTrue("should be able to add to pending upload",
+                result.canAsyncUpload());
+
+            f = File.createTempFile("test", "tmp", tempDir);
+            fos = new FileOutputStream(f);
+            fos.write(byteMap.get("a2"));
+            fos.close();
+            result = cache.store("a2", f, true);
+            assertTrue("should be able to add to pending upload",
+                result.canAsyncUpload());
+
+            f = File.createTempFile("test", "tmp", tempDir);
+            fos = new FileOutputStream(f);
+            fos.write(byteMap.get("a3"));
+            fos.close();
+            result = cache.store("a3", f, true);
+            assertTrue("should be able to add to pending upload",
+                result.canAsyncUpload());
+
+            InputStream inp = cache.getIfStored("a1");
+            assertEquals(new ByteArrayInputStream(byteMap.get("a1")), inp);
+            IOUtils.closeQuietly(inp);
+            inp = cache.getIfStored("a2");
+            assertEquals(new ByteArrayInputStream(byteMap.get("a2")), inp);
+            IOUtils.closeQuietly(inp);
+            inp = cache.getIfStored("a3");
+            assertEquals(new ByteArrayInputStream(byteMap.get("a3")), inp);
+            IOUtils.closeQuietly(inp);
+
+            data = new byte[90];
+            random.nextBytes(data);
+            byteMap.put("a4", data);
+
+            f = File.createTempFile("test", "tmp", tempDir);
+            fos = new FileOutputStream(f);
+            fos.write(byteMap.get("a4"));
+            fos.close();
+
+            result = cache.store("a4", f, true);
+            assertFalse("should not be able to add to pending upload",
+                result.canAsyncUpload());
+            Thread.sleep(1000);
+
+            inp = cache.getIfStored("a1");
+            assertEquals(new ByteArrayInputStream(byteMap.get("a1")), inp);
+            IOUtils.closeQuietly(inp);
+            inp = cache.getIfStored("a2");
+            assertEquals(new ByteArrayInputStream(byteMap.get("a2")), inp);
+            IOUtils.closeQuietly(inp);
+            inp = cache.getIfStored("a3");
+            assertEquals(new ByteArrayInputStream(byteMap.get("a3")), inp);
+            IOUtils.closeQuietly(inp);
+            inp = cache.getIfStored("a4");
+            assertNull("a4 should be null", inp);
+        } catch (Exception e) {
+            LOG.error("error:", e);
+            fail();
+        }
+    }
+
+    /**
+     * Test concurrent {@link LocalCache} initialization with storing
+     * {@link LocalCache}
+     */
+    public void testConcurrentInitWithStore() {
+        try {
+            AsyncUploadCache pendingFiles = new AsyncUploadCache();
+            pendingFiles.init(tempDirPath, cacheDirPath, 100);
+            pendingFiles.reset();
+            LocalCache cache = new LocalCache(cacheDirPath, tempDirPath,
+                10000000, 0.95, 0.70, pendingFiles);
+            Random random = new Random(12345);
+            int fileUploads = 1000;
+            Map<String, byte[]> byteMap = new HashMap<String, byte[]>(
+                fileUploads);
+            byte[] data;
+            for (int i = 0; i < fileUploads; i++) {
+                data = new byte[100];
+                random.nextBytes(data);
+                String key = "a" + i;
+                byteMap.put(key, data);
+                cache.store(key, new ByteArrayInputStream(byteMap.get(key)));
+            }
+            cache.close();
+
+            ExecutorService executor = Executors.newFixedThreadPool(10,
+                new NamedThreadFactory("localcache-store-worker"));
+            cache = new LocalCache(cacheDirPath, tempDirPath, 10000000, 0.95,
+                0.70, pendingFiles);
+            executor.execute(new StoreWorker(cache, byteMap));
+            executor.shutdown();
+            while (!executor.awaitTermination(15, TimeUnit.SECONDS)) {
+            }
+        } catch (Exception e) {
+            LOG.error("error:", e);
+            fail();
+        }
+    }
+
+    private class StoreWorker implements Runnable {
+        Map<String, byte[]> byteMap;
+
+        LocalCache cache;
+
+        Random random;
+
+        private StoreWorker(LocalCache cache, Map<String, byte[]> byteMap) {
+            this.byteMap = byteMap;
+            this.cache = cache;
+            random = new Random(byteMap.size());
+        }
+
+        public void run() {
+            try {
+                for (int i = 0; i < 100; i++) {
+                    String key = "a" + random.nextInt(byteMap.size());
+                    LOG.debug("key=" + key);
+                    cache.store(key, new ByteArrayInputStream(byteMap.get(key)));
+                }
+            } catch (Exception e) {
+                LOG.error("error:", e);
+                fail();
+            }
+        }
+    }
+
+    /**
+     * Assert two inputstream
+     */
+    protected void assertEquals(InputStream a, InputStream b)
+                    throws IOException {
+        while (true) {
+            int ai = a.read();
+            int bi = b.read();
+            assertEquals(ai, bi);
+            if (ai < 0) {
+                break;
+            }
+        }
+        IOUtils.closeQuietly(a);
+        IOUtils.closeQuietly(b);
+    }
+
+}
diff --git a/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/db/ResettableTempFileInputStreamTest.java b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/db/ResettableTempFileInputStreamTest.java
new file mode 100644
index 0000000..154a7ee
--- /dev/null
+++ b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/db/ResettableTempFileInputStreamTest.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data.db;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+
+import junit.framework.TestCase;
+import static org.junit.Assume.assumeTrue;
+
+public class ResettableTempFileInputStreamTest extends TestCase {
+
+    public void testResetStreamAllowsReadAgain() throws Exception {
+        final File tmp = createTemporaryFileWithContents(new byte[1]);
+        ResettableTempFileInputStream in = null;
+        try {
+            in = new ResettableTempFileInputStream(tmp);
+            assertEquals(0, in.read());
+            assertEquals(-1, in.read());
+            in.reset();
+            assertEquals(0, in.read());
+            assertEquals(-1, in.read());
+        } finally {
+            IOUtils.closeQuietly(in);
+        }
+    }
+
+    public void testMarkStreamAllowsReadFromMark() throws Exception {
+        final File tmp = createTemporaryFileWithContents(createTestByteArray());
+        ResettableTempFileInputStream in = null;
+        try {
+            in = new ResettableTempFileInputStream(tmp);
+            assumeTrue(in.read(new byte[5]) == 5);
+            in.mark(Integer.MAX_VALUE);
+            assumeTrue(in.read(new byte[5]) == 5);
+            in.reset();
+            assertEquals(5, in.read());
+        } finally {
+            IOUtils.closeQuietly(in);
+        }
+    }
+
+    private File createTemporaryFileWithContents(byte[] data) throws IOException {
+        final File tmp = File.createTempFile("test", ".bin");
+        FileUtils.writeByteArrayToFile(tmp, data);
+        return tmp;
+    }
+
+    private byte[] createTestByteArray() {
+        byte[] bytes = new byte[10];
+        for (int i = 0; i < 10; i++) {
+            bytes[i] = (byte) i;
+        }
+        return bytes;
+    }
+}
diff --git a/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/db/TempFileInputStreamTest.java b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/db/TempFileInputStreamTest.java
new file mode 100644
index 0000000..7e0bfbc
--- /dev/null
+++ b/jackrabbit-data/src/test/java/org/apache/jackrabbit/core/data/db/TempFileInputStreamTest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.data.db;
+
+import java.io.File;
+
+import junit.framework.TestCase;
+
+public class TempFileInputStreamTest extends TestCase {
+
+    private File tmp;
+    private TempFileInputStream in;
+
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        tmp = File.createTempFile("test", ".bin");
+        in = new TempFileInputStream(tmp);
+    }
+
+    public void testFileIsDeletedWhenStreamIsClosed() throws Exception {
+        assertTrue(tmp.exists());
+        in.close();
+        assertFalse(tmp.exists());
+    }
+
+}
diff --git a/jackrabbit-data/src/test/resources/fs.properties b/jackrabbit-data/src/test/resources/fs.properties
new file mode 100644
index 0000000..9c314cb
--- /dev/null
+++ b/jackrabbit-data/src/test/resources/fs.properties
@@ -0,0 +1,17 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+fsBackendPath=
diff --git a/jackrabbit-data/src/test/resources/log4j.properties b/jackrabbit-data/src/test/resources/log4j.properties
new file mode 100644
index 0000000..8645035
--- /dev/null
+++ b/jackrabbit-data/src/test/resources/log4j.properties
@@ -0,0 +1,29 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# this is the log4j configuration for the JCR API tests
+log4j.rootLogger=INFO, file
+
+#log4j.logger.org.apache.jackrabbit.test=DEBUG
+
+# 'file' is set to be a FileAppender.
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.File=target/debug.log
+
+# 'file' uses PatternLayout.
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{dd.MM.yyyy HH:mm:ss} *%-5p* [%t] %c{1}: %m (%F, line %L)\n
diff --git a/jackrabbit-jca/deploy/geronimo/geronimo-ra.xml b/jackrabbit-jca/deploy/geronimo/geronimo-ra.xml
new file mode 100644
index 0000000..9354038
--- /dev/null
+++ b/jackrabbit-jca/deploy/geronimo/geronimo-ra.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+<connector xmlns="http://geronimo.apache.org/xml/ns/j2ee/connector-1.2">
+
+  <dep:environment xmlns:dep="http://geronimo.apache.org/xml/ns/deployment-1.2">
+    <dep:moduleId>
+      <dep:groupId>org.apache.jackrabbit</dep:groupId>
+      <dep:artifactId>jackrabbit-jca</dep:artifactId>
+      <dep:version>1.0</dep:version>
+      <dep:type>rar</dep:type>
+    </dep:moduleId>
+     <dep:dependencies>
+      <dep:dependency>
+        <dep:groupId>javax.jcr</dep:groupId>
+        <dep:artifactId>jcr</dep:artifactId>
+        <dep:version>2.0</dep:version>
+        <dep:type>jar</dep:type>
+      </dep:dependency>
+    </dep:dependencies>
+  </dep:environment>
+
+  <resourceadapter>
+    <outbound-resourceadapter>
+      <connection-definition>
+        <connectionfactory-interface>javax.jcr.Repository</connectionfactory-interface>
+        <connectiondefinition-instance>
+          <name>jackrabbit</name>
+          <config-property-setting name="RepositoryURI">jcr-jackrabbit://jackrabbit</config-property-setting>
+          <connectionmanager>
+            <local-transaction/>
+            <single-pool>
+              <max-size>10</max-size>
+              <min-size>0</min-size>
+              <match-one/>
+            </single-pool>
+          </connectionmanager>
+        </connectiondefinition-instance>
+      </connection-definition>
+    </outbound-resourceadapter>
+  </resourceadapter>
+
+</connector>
diff --git a/jackrabbit-jca/pom.xml b/jackrabbit-jca/pom.xml
index 7fbafe2..9a15d75 100644
--- a/jackrabbit-jca/pom.xml
+++ b/jackrabbit-jca/pom.xml
@@ -26,7 +26,7 @@
   <parent>
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>jackrabbit-parent</artifactId>
-    <version>2.3.6</version>
+    <version>2.10.1</version>
     <relativePath>../jackrabbit-parent/pom.xml</relativePath>
   </parent>
   <artifactId>jackrabbit-jca</artifactId>
@@ -108,7 +108,7 @@
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-core</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.tika</groupId>
diff --git a/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/JCAConnectionRequestInfo.java b/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/JCAConnectionRequestInfo.java
index b0174ac..23375a5 100644
--- a/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/JCAConnectionRequestInfo.java
+++ b/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/JCAConnectionRequestInfo.java
@@ -152,16 +152,16 @@ public final class JCAConnectionRequestInfo implements ConnectionRequestInfo {
             return false;
         }
 
-        Map m1 = getAttributeMap(o1);
-        Map m2 = getAttributeMap(o2);
+        Map<String, Object> m1 = getAttributeMap(o1);
+        Map<String, Object> m2 = getAttributeMap(o2);
         return m1.equals(m2);
     }
 
     /**
      * Return the credentials attributes.
      */
-    private Map getAttributeMap(SimpleCredentials creds) {
-        HashMap map = new HashMap();
+    private Map<String, Object> getAttributeMap(SimpleCredentials creds) {
+        HashMap<String, Object> map = new HashMap<String, Object>();
         String[] keys = creds.getAttributeNames();
 
         for (int i = 0; i < keys.length; i++) {
@@ -189,7 +189,7 @@ public final class JCAConnectionRequestInfo implements ConnectionRequestInfo {
     private int computeSimpleCredsHashCode(SimpleCredentials c) {
         String userID = c.getUserID();
         char[] password = c.getPassword();
-        Map m = getAttributeMap(c);
+        Map<String, Object> m = getAttributeMap(c);
         final int prime = 31;
         int result = 1;
         result = prime * result + ((userID == null) ? 0 : userID.hashCode());
@@ -199,4 +199,9 @@ public final class JCAConnectionRequestInfo implements ConnectionRequestInfo {
         result = prime * result + ((m == null) ? 0 : m.hashCode());
         return result;
     }
+    
+    @Override
+    public String toString() {
+        return "workspace (" + workspace + ") creds (" + creds + ")";
+    }
 }
diff --git a/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/JCAManagedConnection.java b/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/JCAManagedConnection.java
index 107e590..c7f6bb4 100644
--- a/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/JCAManagedConnection.java
+++ b/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/JCAManagedConnection.java
@@ -27,10 +27,14 @@ import javax.resource.spi.LocalTransaction;
 import javax.resource.spi.ManagedConnection;
 import javax.resource.spi.ManagedConnectionMetaData;
 import javax.security.auth.Subject;
+import javax.transaction.xa.XAException;
 import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
 import java.io.PrintWriter;
 import java.util.Iterator;
 import java.util.LinkedList;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * This class implements the managed connection for
@@ -40,6 +44,112 @@ public class JCAManagedConnection
         implements ManagedConnection, ManagedConnectionMetaData {
 
     /**
+     * The LocalTransactionAdapter wraps the internal XAResource and uses the XA Method's to
+     * fulfill the LocalTransaction calls.
+     */
+    private static class LocalTransactionAdapter implements javax.resource.spi.LocalTransaction {
+
+        /**
+         * Internal {@link Xid} implementation.
+         */
+        class XidImpl implements Xid {
+        
+            private final byte[] globalTxId;
+        
+            public XidImpl(byte[] globalTxId) {
+                this.globalTxId = globalTxId;
+            }
+        
+            /**
+             * {@inheritDoc}
+             */
+            public int getFormatId() {
+                return 0;
+            }
+        
+            /**
+             * {@inheritDoc}
+             */
+            public byte[] getBranchQualifier() {
+                return new byte[0];
+            }
+        
+            /**
+             * {@inheritDoc}
+             */
+            public byte[] getGlobalTransactionId() {
+                return globalTxId;
+            }
+        }
+
+        /**
+         * Global static counter for the internal Xid's
+         */
+        private static AtomicInteger globalCounter = new AtomicInteger();
+
+        private XAResource resource;
+        private Xid xid;
+
+        public LocalTransactionAdapter(XAResource xaResource) {
+            this.resource = xaResource;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void begin() throws ResourceException {
+            try {
+                this.xid = new XidImpl(intToByteArray(globalCounter.getAndIncrement()));
+                resource.start(xid, XAResource.TMNOFLAGS);
+            } catch (XAException e) {
+                throw new ResourceException(e.getMessage());
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void commit() throws ResourceException {
+            try {
+                resource.end(xid, XAResource.TMSUCCESS);
+                resource.commit(xid, true);
+            } catch (XAException e) {
+                throw new ResourceException(e.getMessage());
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void rollback() throws ResourceException {
+            try {
+                resource.end(xid, XAResource.TMFAIL);
+                resource.rollback(xid);
+            } catch (XAException e) {
+                throw new ResourceException(e.getMessage());
+            }
+        }
+        
+        /**
+         * Converts the given int (global transaction id) to a byte[]
+         * 
+         * @param value
+         * @return byte[]
+         */
+        private static byte[] intToByteArray(int value) {
+            byte[] b = new byte[4];
+            for (int i = 0; i < 4; i++) {
+                int offset = (b.length - 1 - i) * 8;
+                b[i] = (byte) ((value >>> offset) & 0xFF);
+            }
+            return b;
+        }
+    }
+
+    /**
      * Managed connection factory.
      */
     private final JCAManagedConnectionFactory mcf;
@@ -74,6 +184,8 @@ public class JCAManagedConnection
      */
     private PrintWriter logWriter;
 
+    private LocalTransactionAdapter localTransactionAdapter;
+
     /**
      * Construct the managed connection.
      */
@@ -86,10 +198,11 @@ public class JCAManagedConnection
         this.listeners = new LinkedList<ConnectionEventListener>();
         this.handles = new LinkedList<JCASessionHandle>();
         if (this.mcf.getBindSessionToTransaction().booleanValue()) {
-            this.xaResource =  new TransactionBoundXAResource(this, (XAResource) session);
+            this.xaResource = new TransactionBoundXAResource(this, (XAResource) session);
         } else {
             this.xaResource = (XAResource) session;
         }
+        this.localTransactionAdapter = new LocalTransactionAdapter(xaResource);
     }
 
     /**
@@ -204,7 +317,7 @@ public class JCAManagedConnection
      */
     public LocalTransaction getLocalTransaction()
             throws ResourceException {
-        throw new UnsupportedOperationException("Local transaction is not supported");
+        return localTransactionAdapter;
     }
 
     /**
diff --git a/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/JCAManagedConnectionFactory.java b/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/JCAManagedConnectionFactory.java
index 450b69f..10f8055 100644
--- a/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/JCAManagedConnectionFactory.java
+++ b/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/JCAManagedConnectionFactory.java
@@ -63,6 +63,13 @@ public class JCAManagedConnectionFactory
     private Boolean bindSessionToTransaction = Boolean.TRUE;
 
     /**
+     * Flag indicating whether the Repository should start
+     * immediately or lazy on first access. Per default true so the Repository will
+     * start immediately if this JCAManagedConnectionFactory will be initialized.
+     */
+    private Boolean startRepositoryImmediately = Boolean.TRUE;
+
+    /**
      * Repository.
      */
     private transient Repository repository;
@@ -142,6 +149,9 @@ public class JCAManagedConnectionFactory
      */
     public Object createConnectionFactory(ConnectionManager cm)
             throws ResourceException {
+    	if (startRepositoryImmediately) {
+    		createRepository();
+    	}
         JCARepositoryHandle handle = new JCARepositoryHandle(this, cm);
         log("Created repository handle (" + handle + ")");
         return handle;
@@ -149,9 +159,9 @@ public class JCAManagedConnectionFactory
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * Creates a new physical connection to the underlying EIS resource manager.
-     * <p/>
+     * <p>
      * WebSphere 5.1.1 will try to recover an XA resource on startup, regardless
      * whether it was committed or rolled back. On this occasion, <code>cri</code>
      * will be <code>null</code>. In order to be interoperable, we return an
@@ -192,23 +202,47 @@ public class JCAManagedConnectionFactory
                 }
             }
         }
-
         return null;
     }
 
     /**
-     * Return the repository, automatically creating it if needed.
+     * Create/startup the repository.
      */
-    public synchronized Repository getRepository() throws RepositoryException {
+    @SuppressWarnings("deprecation")
+    private void createRepository() throws ResourceException {
         if (repository == null) {
-            JCARepositoryManager mgr = JCARepositoryManager.getInstance();
-            repository = mgr.createRepository(parameters);
-            log("Created repository (" + repository + ")");
+            try {
+                JCARepositoryManager mgr = JCARepositoryManager.getInstance();
+                repository = mgr.createRepository(parameters);
+                log("Created repository (" + repository + ")");
+            } catch (RepositoryException e) {
+                log("Failed to create repository", e);
+                ResourceException exception = new ResourceException(
+                        "Failed to create repository: " + e.getMessage());
+                exception.setLinkedException(e);
+                throw exception;
+            }
         }
-        return repository;
     }
 
     /**
+     * Return the repository, automatically creating it if needed.
+     */
+    @SuppressWarnings("deprecation")
+	public Repository getRepository() throws RepositoryException {
+    	if (repository == null) {
+    		synchronized (this) {
+    			try {
+    				createRepository();
+    			} catch (ResourceException e) {
+    				throw (RepositoryException) e.getLinkedException();
+    			}
+			}
+    	}
+        return repository;
+    }
+    
+    /**
      * Log a message.
      */
     public void log(String message) {
@@ -271,4 +305,12 @@ public class JCAManagedConnectionFactory
         this.bindSessionToTransaction = bindSessionToTransaction;
     }
 
+    public Boolean getStartRepositoryImmediately() {
+        return startRepositoryImmediately;
+    }
+
+    public void setStartRepositoryImmediately(Boolean startRepositoryImmediately) {
+        this.startRepositoryImmediately = startRepositoryImmediately;
+    }
+
 }
diff --git a/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/JCARepositoryManager.java b/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/JCARepositoryManager.java
index 0ecb649..4323abc 100644
--- a/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/JCARepositoryManager.java
+++ b/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/JCARepositoryManager.java
@@ -24,6 +24,7 @@ import org.apache.jackrabbit.commons.JcrUtils;
 import org.apache.jackrabbit.core.RepositoryImpl;
 import org.apache.jackrabbit.core.config.RepositoryConfig;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.HashMap;
@@ -115,8 +116,10 @@ public final class JCARepositoryManager {
                     }
                 }
             }            	
-        } else {
+        } else if (configFile != null) {
             config = RepositoryConfig.create(configFile, homeDir);
+        } else {
+            config = RepositoryConfig.create(new File(homeDir));
         }
         return RepositoryImpl.create(config);
 	}
@@ -151,9 +154,6 @@ public final class JCARepositoryManager {
     /**
      * Try to shutdown the repository only if
      * {@link JCARepositoryManager#autoShutdown} is true.
-     *
-     * @param homeDir   The location of the repository.
-     * @param configFile The path to the repository configuration file.
      */
     public synchronized void autoShutdownRepository(
             Map<String, String> parameters) {
diff --git a/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/TransactionBoundXAResource.java b/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/TransactionBoundXAResource.java
index 8632257..92700ff 100644
--- a/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/TransactionBoundXAResource.java
+++ b/jackrabbit-jca/src/main/java/org/apache/jackrabbit/jca/TransactionBoundXAResource.java
@@ -55,7 +55,9 @@ public class TransactionBoundXAResource implements XAResource {
             try {
                 xaResource.end(arg0, arg1);
             } finally {
-                this.connection.closeHandles();
+            	if(arg1 != XAResource.TMSUSPEND) {
+            		this.connection.closeHandles();
+            	}
             }
             // reuse the XAResource
             this.ending = false;
diff --git a/jackrabbit-jca/src/main/rar/META-INF/LICENSE b/jackrabbit-jca/src/main/rar/META-INF/LICENSE
index 1c2d98c..82ea725 100644
--- a/jackrabbit-jca/src/main/rar/META-INF/LICENSE
+++ b/jackrabbit-jca/src/main/rar/META-INF/LICENSE
@@ -924,3 +924,702 @@ Array utility code in Lucene Java (lucene-core)
     WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
     OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+AspectJ runtime library (aspectjrt)
+
+    Eclipse Public License - v 1.0
+
+    THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+    PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF
+    THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+    1. DEFINITIONS
+
+    "Contribution" means:
+
+    a) in the case of the initial Contributor, the initial code and
+       documentation distributed under this Agreement, and
+
+    b) in the case of each subsequent Contributor:
+
+       i) changes to the Program, and
+
+       ii) additions to the Program;
+
+       where such changes and/or additions to the Program originate from and
+       are distributed by that particular Contributor. A Contribution
+       'originates' from a Contributor if it was added to the Program by
+       such Contributor itself or anyone acting on such Contributor's behalf.
+       Contributions do not include additions to the Program which: (i) are
+       separate modules of software distributed in conjunction with the
+       Program under their own license agreement, and (ii) are not derivative
+       works of the Program.
+
+    "Contributor" means any person or entity that distributes the Program.
+
+    "Licensed Patents " mean patent claims licensable by a Contributor which
+    are necessarily infringed by the use or sale of its Contribution alone or
+    when combined with the Program.
+
+    "Program" means the Contributions distributed in accordance with this
+    Agreement.
+
+    "Recipient" means anyone who receives the Program under this Agreement,
+    including all Contributors.
+
+    2. GRANT OF RIGHTS
+
+    a) Subject to the terms of this Agreement, each Contributor hereby grants
+       Recipient a non-exclusive, worldwide, royalty-free copyright license to
+       reproduce, prepare derivative works of, publicly display, publicly
+       perform, distribute and sublicense the Contribution of such
+       Contributor, if any, and such derivative works, in source code and
+       object code form.
+
+    b) Subject to the terms of this Agreement, each Contributor hereby grants
+       Recipient a non-exclusive, worldwide, royalty-free patent license under
+       Licensed Patents to make, use, sell, offer to sell, import and
+       otherwise transfer the Contribution of such Contributor, if any, in
+       source code and object code form. This patent license shall apply to
+       the combination of the Contribution and the Program if, at the time
+       the Contribution is added by the Contributor, such addition of the
+       Contribution causes such combination to be covered by the Licensed
+       Patents. The patent license shall not apply to any other combinations
+       which include the Contribution. No hardware per se is licensed hereunder.
+
+    c) Recipient understands that although each Contributor grants the
+       licenses to its Contributions set forth herein, no assurances are
+       provided by any Contributor that the Program does not infringe the
+       patent or other intellectual property rights of any other entity. Each
+       Contributor disclaims any liability to Recipient for claims brought by
+       any other entity based on infringement of intellectual property rights
+       or otherwise. As a condition to exercising the rights and licenses
+       granted hereunder, each Recipient hereby assumes sole responsibility
+       to secure any other intellectual property rights needed, if any. For
+       example, if a third party patent license is required to allow Recipient
+       to distribute the Program, it is Recipient's responsibility to acquire
+       that license before distributing the Program.
+
+    d) Each Contributor represents that to its knowledge it has sufficient
+       copyright rights in its Contribution, if any, to grant the copyright
+       license set forth in this Agreement.
+
+    3. REQUIREMENTS
+
+    A Contributor may choose to distribute the Program in object code form
+    under its own license agreement, provided that:
+
+    a) it complies with the terms and conditions of this Agreement; and
+
+    b) its license agreement:
+
+       i)   effectively disclaims on behalf of all Contributors all warranties
+            and conditions, express and implied, including warranties or
+            conditions of title and non-infringement, and implied warranties
+            or conditions of merchantability and fitness for a particular
+            purpose;
+
+       ii)  effectively excludes on behalf of all Contributors all liability
+            for damages, including direct, indirect, special, incidental and
+            consequential damages, such as lost profits;
+
+       iii) states that any provisions which differ from this Agreement are
+            offered by that Contributor alone and not by any other party; and
+
+       iv)  states that source code for the Program is available from such
+            Contributor, and informs licensees how to obtain it in a
+            reasonable manner on or through a medium customarily used for
+            software exchange.
+
+    When the Program is made available in source code form:
+
+    a) it must be made available under this Agreement; and
+
+    b) a copy of this Agreement must be included with each copy of the
+       Program.
+
+    Contributors may not remove or alter any copyright notices contained
+    within the Program.
+
+    Each Contributor must identify itself as the originator of its
+    Contribution, if any, in a manner that reasonably allows subsequent
+    Recipients to identify the originator of the Contribution.
+
+    4. COMMERCIAL DISTRIBUTION
+
+    Commercial distributors of software may accept certain responsibilities
+    with respect to end users, business partners and the like. While this
+    license is intended to facilitate the commercial use of the Program,
+    the Contributor who includes the Program in a commercial product offering
+    should do so in a manner which does not create potential liability for
+    other Contributors. Therefore, if a Contributor includes the Program in
+    a commercial product offering, such Contributor ("Commercial Contributor")
+    hereby agrees to defend and indemnify every other Contributor
+    ("Indemnified Contributor") against any losses, damages and costs
+    (collectively "Losses") arising from claims, lawsuits and other legal
+    actions brought by a third party against the Indemnified Contributor to
+    the extent caused by the acts or omissions of such Commercial Contributor
+    in connection with its distribution of the Program in a commercial
+    product offering. The obligations in this section do not apply to any
+    claims or Losses relating to any actual or alleged intellectual property
+    infringement. In order to qualify, an Indemnified Contributor must:
+    a) promptly notify the Commercial Contributor in writing of such claim,
+    and b) allow the Commercial Contributor to control, and cooperate with
+    the Commercial Contributor in, the defense and any related settlement
+    negotiations. The Indemnified Contributor may participate in any such
+    claim at its own expense.
+
+    For example, a Contributor might include the Program in a commercial
+    product offering, Product X. That Contributor is then a Commercial
+    Contributor. If that Commercial Contributor then makes performance claims,
+    or offers warranties related to Product X, those performance claims and
+    warranties are such Commercial Contributor's responsibility alone. Under
+    this section, the Commercial Contributor would have to defend claims
+    against the other Contributors related to those performance claims and
+    warranties, and if a court requires any other Contributor to pay any
+    damages as a result, the Commercial Contributor must pay those damages.
+
+    5. NO WARRANTY
+
+    EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED
+    ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
+    EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR
+    CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A
+    PARTICULAR PURPOSE. Each Recipient is solely responsible for determining
+    the appropriateness of using and distributing the Program and assumes all
+    risks associated with its exercise of rights under this Agreement ,
+    including but not limited to the risks and costs of program errors,
+    compliance with applicable laws, damage to or loss of data, programs or
+    equipment, and unavailability or interruption of operations.
+
+    6. DISCLAIMER OF LIABILITY
+
+    EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR
+    ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
+    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
+    WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
+    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
+    DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+    HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+    7. GENERAL
+
+    If any provision of this Agreement is invalid or unenforceable under
+    applicable law, it shall not affect the validity or enforceability of
+    the remainder of the terms of this Agreement, and without further action
+    by the parties hereto, such provision shall be reformed to the minimum
+    extent necessary to make such provision valid and enforceable.
+
+    If Recipient institutes patent litigation against any entity (including
+    a cross-claim or counterclaim in a lawsuit) alleging that the Program
+    itself (excluding combinations of the Program with other software or
+    hardware) infringes such Recipient's patent(s), then such Recipient's
+    rights granted under Section 2(b) shall terminate as of the date such
+    litigation is filed.
+
+    All Recipient's rights under this Agreement shall terminate if it fails
+    to comply with any of the material terms or conditions of this Agreement
+    and does not cure such failure in a reasonable period of time after
+    becoming aware of such noncompliance. If all Recipient's rights under
+    this Agreement terminate, Recipient agrees to cease use and distribution
+    of the Program as soon as reasonably practicable. However, Recipient's
+    obligations under this Agreement and any licenses granted by Recipient
+    relating to the Program shall continue and survive.
+
+    Everyone is permitted to copy and distribute copies of this Agreement,
+    but in order to avoid inconsistency the Agreement is copyrighted and may
+    only be modified in the following manner. The Agreement Steward reserves
+    the right to publish new versions (including revisions) of this Agreement
+    from time to time. No one other than the Agreement Steward has the right
+    to modify this Agreement. The Eclipse Foundation is the initial Agreement
+    Steward. The Eclipse Foundation may assign the responsibility to serve as
+    the Agreement Steward to a suitable separate entity. Each new version of
+    the Agreement will be given a distinguishing version number. The Program
+    (including Contributions) may always be distributed subject to the version
+    of the Agreement under which it was received. In addition, after a new
+    version of the Agreement is published, Contributor may elect to distribute
+    the Program (including its Contributions) under the new version. Except as
+    expressly stated in Sections 2(a) and 2(b) above, Recipient receives no
+    rights or licenses to the intellectual property of any Contributor under
+    this Agreement, whether expressly, by implication, estoppel or otherwise.
+    All rights in the Program not expressly granted under this Agreement
+    are reserved.
+
+    This Agreement is governed by the laws of the State of New York and the
+    intellectual property laws of the United States of America. No party to
+    this Agreement will bring a legal action under this Agreement more than
+    one year after the cause of action arose. Each party waives its rights to
+    a jury trial in any resulting litigation.
+
+juniversalchardet library (juniversalchardet)
+
+                              MOZILLA PUBLIC LICENSE
+                                    Version 1.1
+
+                                  ---------------
+
+    1. Definitions.
+
+         1.0.1. "Commercial Use" means distribution or otherwise making the
+         Covered Code available to a third party.
+
+         1.1. "Contributor" means each entity that creates or contributes to
+         the creation of Modifications.
+
+         1.2. "Contributor Version" means the combination of the Original
+         Code, prior Modifications used by a Contributor, and the Modifications
+         made by that particular Contributor.
+
+         1.3. "Covered Code" means the Original Code or Modifications or the
+         combination of the Original Code and Modifications, in each case
+         including portions thereof.
+
+         1.4. "Electronic Distribution Mechanism" means a mechanism generally
+         accepted in the software development community for the electronic
+         transfer of data.
+
+         1.5. "Executable" means Covered Code in any form other than Source
+         Code.
+
+         1.6. "Initial Developer" means the individual or entity identified
+         as the Initial Developer in the Source Code notice required by Exhibit
+         A.
+
+         1.7. "Larger Work" means a work which combines Covered Code or
+         portions thereof with code not governed by the terms of this License.
+
+         1.8. "License" means this document.
+
+         1.8.1. "Licensable" means having the right to grant, to the maximum
+         extent possible, whether at the time of the initial grant or
+         subsequently acquired, any and all of the rights conveyed herein.
+
+         1.9. "Modifications" means any addition to or deletion from the
+         substance or structure of either the Original Code or any previous
+         Modifications. When Covered Code is released as a series of files, a
+         Modification is:
+              A. Any addition to or deletion from the contents of a file
+              containing Original Code or previous Modifications.
+
+              B. Any new file that contains any part of the Original Code or
+              previous Modifications.
+
+         1.10. "Original Code" means Source Code of computer software code
+         which is described in the Source Code notice required by Exhibit A as
+         Original Code, and which, at the time of its release under this
+         License is not already Covered Code governed by this License.
+
+         1.10.1. "Patent Claims" means any patent claim(s), now owned or
+         hereafter acquired, including without limitation,  method, process,
+         and apparatus claims, in any patent Licensable by grantor.
+
+         1.11. "Source Code" means the preferred form of the Covered Code for
+         making modifications to it, including all modules it contains, plus
+         any associated interface definition files, scripts used to control
+         compilation and installation of an Executable, or source code
+         differential comparisons against either the Original Code or another
+         well known, available Covered Code of the Contributor's choice. The
+         Source Code can be in a compressed or archival form, provided the
+         appropriate decompression or de-archiving software is widely available
+         for no charge.
+
+         1.12. "You" (or "Your")  means an individual or a legal entity
+         exercising rights under, and complying with all of the terms of, this
+         License or a future version of this License issued under Section 6.1.
+         For legal entities, "You" includes any entity which controls, is
+         controlled by, or is under common control with You. For purposes of
+         this definition, "control" means (a) the power, direct or indirect,
+         to cause the direction or management of such entity, whether by
+         contract or otherwise, or (b) ownership of more than fifty percent
+         (50%) of the outstanding shares or beneficial ownership of such
+         entity.
+
+    2. Source Code License.
+
+         2.1. The Initial Developer Grant.
+         The Initial Developer hereby grants You a world-wide, royalty-free,
+         non-exclusive license, subject to third party intellectual property
+         claims:
+              (a)  under intellectual property rights (other than patent or
+              trademark) Licensable by Initial Developer to use, reproduce,
+              modify, display, perform, sublicense and distribute the Original
+              Code (or portions thereof) with or without Modifications, and/or
+              as part of a Larger Work; and
+
+              (b) under Patents Claims infringed by the making, using or
+              selling of Original Code, to make, have made, use, practice,
+              sell, and offer for sale, and/or otherwise dispose of the
+              Original Code (or portions thereof).
+
+              (c) the licenses granted in this Section 2.1(a) and (b) are
+              effective on the date Initial Developer first distributes
+              Original Code under the terms of this License.
+
+              (d) Notwithstanding Section 2.1(b) above, no patent license is
+              granted: 1) for code that You delete from the Original Code; 2)
+              separate from the Original Code;  or 3) for infringements caused
+              by: i) the modification of the Original Code or ii) the
+              combination of the Original Code with other software or devices.
+
+         2.2. Contributor Grant.
+         Subject to third party intellectual property claims, each Contributor
+         hereby grants You a world-wide, royalty-free, non-exclusive license
+
+              (a)  under intellectual property rights (other than patent or
+              trademark) Licensable by Contributor, to use, reproduce, modify,
+              display, perform, sublicense and distribute the Modifications
+              created by such Contributor (or portions thereof) either on an
+              unmodified basis, with other Modifications, as Covered Code
+              and/or as part of a Larger Work; and
+
+              (b) under Patent Claims infringed by the making, using, or
+              selling of  Modifications made by that Contributor either alone
+              and/or in combination with its Contributor Version (or portions
+              of such combination), to make, use, sell, offer for sale, have
+              made, and/or otherwise dispose of: 1) Modifications made by that
+              Contributor (or portions thereof); and 2) the combination of
+              Modifications made by that Contributor with its Contributor
+              Version (or portions of such combination).
+
+              (c) the licenses granted in Sections 2.2(a) and 2.2(b) are
+              effective on the date Contributor first makes Commercial Use of
+              the Covered Code.
+
+              (d)    Notwithstanding Section 2.2(b) above, no patent license is
+              granted: 1) for any code that Contributor has deleted from the
+              Contributor Version; 2)  separate from the Contributor Version;
+              3)  for infringements caused by: i) third party modifications of
+              Contributor Version or ii)  the combination of Modifications made
+              by that Contributor with other software  (except as part of the
+              Contributor Version) or other devices; or 4) under Patent Claims
+              infringed by Covered Code in the absence of Modifications made by
+              that Contributor.
+
+    3. Distribution Obligations.
+
+         3.1. Application of License.
+         The Modifications which You create or to which You contribute are
+         governed by the terms of this License, including without limitation
+         Section 2.2. The Source Code version of Covered Code may be
+         distributed only under the terms of this License or a future version
+         of this License released under Section 6.1, and You must include a
+         copy of this License with every copy of the Source Code You
+         distribute. You may not offer or impose any terms on any Source Code
+         version that alters or restricts the applicable version of this
+         License or the recipients' rights hereunder. However, You may include
+         an additional document offering the additional rights described in
+         Section 3.5.
+
+         3.2. Availability of Source Code.
+         Any Modification which You create or to which You contribute must be
+         made available in Source Code form under the terms of this License
+         either on the same media as an Executable version or via an accepted
+         Electronic Distribution Mechanism to anyone to whom you made an
+         Executable version available; and if made available via Electronic
+         Distribution Mechanism, must remain available for at least twelve (12)
+         months after the date it initially became available, or at least six
+         (6) months after a subsequent version of that particular Modification
+         has been made available to such recipients. You are responsible for
+         ensuring that the Source Code version remains available even if the
+         Electronic Distribution Mechanism is maintained by a third party.
+
+         3.3. Description of Modifications.
+         You must cause all Covered Code to which You contribute to contain a
+         file documenting the changes You made to create that Covered Code and
+         the date of any change. You must include a prominent statement that
+         the Modification is derived, directly or indirectly, from Original
+         Code provided by the Initial Developer and including the name of the
+         Initial Developer in (a) the Source Code, and (b) in any notice in an
+         Executable version or related documentation in which You describe the
+         origin or ownership of the Covered Code.
+
+         3.4. Intellectual Property Matters
+              (a) Third Party Claims.
+              If Contributor has knowledge that a license under a third party's
+              intellectual property rights is required to exercise the rights
+              granted by such Contributor under Sections 2.1 or 2.2,
+              Contributor must include a text file with the Source Code
+              distribution titled "LEGAL" which describes the claim and the
+              party making the claim in sufficient detail that a recipient will
+              know whom to contact. If Contributor obtains such knowledge after
+              the Modification is made available as described in Section 3.2,
+              Contributor shall promptly modify the LEGAL file in all copies
+              Contributor makes available thereafter and shall take other steps
+              (such as notifying appropriate mailing lists or newsgroups)
+              reasonably calculated to inform those who received the Covered
+              Code that new knowledge has been obtained.
+
+              (b) Contributor APIs.
+              If Contributor's Modifications include an application programming
+              interface and Contributor has knowledge of patent licenses which
+              are reasonably necessary to implement that API, Contributor must
+              also include this information in the LEGAL file.
+
+                   (c)    Representations.
+              Contributor represents that, except as disclosed pursuant to
+              Section 3.4(a) above, Contributor believes that Contributor's
+              Modifications are Contributor's original creation(s) and/or
+              Contributor has sufficient rights to grant the rights conveyed by
+              this License.
+
+         3.5. Required Notices.
+         You must duplicate the notice in Exhibit A in each file of the Source
+         Code.  If it is not possible to put such notice in a particular Source
+         Code file due to its structure, then You must include such notice in a
+         location (such as a relevant directory) where a user would be likely
+         to look for such a notice.  If You created one or more Modification(s)
+         You may add your name as a Contributor to the notice described in
+         Exhibit A.  You must also duplicate this License in any documentation
+         for the Source Code where You describe recipients' rights or ownership
+         rights relating to Covered Code.  You may choose to offer, and to
+         charge a fee for, warranty, support, indemnity or liability
+         obligations to one or more recipients of Covered Code. However, You
+         may do so only on Your own behalf, and not on behalf of the Initial
+         Developer or any Contributor. You must make it absolutely clear than
+         any such warranty, support, indemnity or liability obligation is
+         offered by You alone, and You hereby agree to indemnify the Initial
+         Developer and every Contributor for any liability incurred by the
+         Initial Developer or such Contributor as a result of warranty,
+         support, indemnity or liability terms You offer.
+
+         3.6. Distribution of Executable Versions.
+         You may distribute Covered Code in Executable form only if the
+         requirements of Section 3.1-3.5 have been met for that Covered Code,
+         and if You include a notice stating that the Source Code version of
+         the Covered Code is available under the terms of this License,
+         including a description of how and where You have fulfilled the
+         obligations of Section 3.2. The notice must be conspicuously included
+         in any notice in an Executable version, related documentation or
+         collateral in which You describe recipients' rights relating to the
+         Covered Code. You may distribute the Executable version of Covered
+         Code or ownership rights under a license of Your choice, which may
+         contain terms different from this License, provided that You are in
+         compliance with the terms of this License and that the license for the
+         Executable version does not attempt to limit or alter the recipient's
+         rights in the Source Code version from the rights set forth in this
+         License. If You distribute the Executable version under a different
+         license You must make it absolutely clear that any terms which differ
+         from this License are offered by You alone, not by the Initial
+         Developer or any Contributor. You hereby agree to indemnify the
+         Initial Developer and every Contributor for any liability incurred by
+         the Initial Developer or such Contributor as a result of any such
+         terms You offer.
+
+         3.7. Larger Works.
+         You may create a Larger Work by combining Covered Code with other code
+         not governed by the terms of this License and distribute the Larger
+         Work as a single product. In such a case, You must make sure the
+         requirements of this License are fulfilled for the Covered Code.
+
+    4. Inability to Comply Due to Statute or Regulation.
+
+         If it is impossible for You to comply with any of the terms of this
+         License with respect to some or all of the Covered Code due to
+         statute, judicial order, or regulation then You must: (a) comply with
+         the terms of this License to the maximum extent possible; and (b)
+         describe the limitations and the code they affect. Such description
+         must be included in the LEGAL file described in Section 3.4 and must
+         be included with all distributions of the Source Code. Except to the
+         extent prohibited by statute or regulation, such description must be
+         sufficiently detailed for a recipient of ordinary skill to be able to
+         understand it.
+
+    5. Application of this License.
+
+         This License applies to code to which the Initial Developer has
+         attached the notice in Exhibit A and to related Covered Code.
+
+    6. Versions of the License.
+
+         6.1. New Versions.
+         Netscape Communications Corporation ("Netscape") may publish revised
+         and/or new versions of the License from time to time. Each version
+         will be given a distinguishing version number.
+
+         6.2. Effect of New Versions.
+         Once Covered Code has been published under a particular version of the
+         License, You may always continue to use it under the terms of that
+         version. You may also choose to use such Covered Code under the terms
+         of any subsequent version of the License published by Netscape. No one
+         other than Netscape has the right to modify the terms applicable to
+         Covered Code created under this License.
+
+         6.3. Derivative Works.
+         If You create or use a modified version of this License (which you may
+         only do in order to apply it to code which is not already Covered Code
+         governed by this License), You must (a) rename Your license so that
+         the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape",
+         "MPL", "NPL" or any confusingly similar phrase do not appear in your
+         license (except to note that your license differs from this License)
+         and (b) otherwise make it clear that Your version of the license
+         contains terms which differ from the Mozilla Public License and
+         Netscape Public License. (Filling in the name of the Initial
+         Developer, Original Code or Contributor in the notice described in
+         Exhibit A shall not of themselves be deemed to be modifications of
+         this License.)
+
+    7. DISCLAIMER OF WARRANTY.
+
+         COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
+         WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+         WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF
+         DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
+         THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE
+         IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT,
+         YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE
+         COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER
+         OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
+         ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
+
+    8. TERMINATION.
+
+         8.1.  This License and the rights granted hereunder will terminate
+         automatically if You fail to comply with terms herein and fail to cure
+         such breach within 30 days of becoming aware of the breach. All
+         sublicenses to the Covered Code which are properly granted shall
+         survive any termination of this License. Provisions which, by their
+         nature, must remain in effect beyond the termination of this License
+         shall survive.
+
+         8.2.  If You initiate litigation by asserting a patent infringement
+         claim (excluding declatory judgment actions) against Initial Developer
+         or a Contributor (the Initial Developer or Contributor against whom
+         You file such action is referred to as "Participant")  alleging that:
+
+         (a)  such Participant's Contributor Version directly or indirectly
+         infringes any patent, then any and all rights granted by such
+         Participant to You under Sections 2.1 and/or 2.2 of this License
+         shall, upon 60 days notice from Participant terminate prospectively,
+         unless if within 60 days after receipt of notice You either: (i)
+         agree in writing to pay Participant a mutually agreeable reasonable
+         royalty for Your past and future use of Modifications made by such
+         Participant, or (ii) withdraw Your litigation claim with respect to
+         the Contributor Version against such Participant.  If within 60 days
+         of notice, a reasonable royalty and payment arrangement are not
+         mutually agreed upon in writing by the parties or the litigation claim
+         is not withdrawn, the rights granted by Participant to You under
+         Sections 2.1 and/or 2.2 automatically terminate at the expiration of
+         the 60 day notice period specified above.
+
+         (b)  any software, hardware, or device, other than such Participant's
+         Contributor Version, directly or indirectly infringes any patent, then
+         any rights granted to You by such Participant under Sections 2.1(b)
+         and 2.2(b) are revoked effective as of the date You first made, used,
+         sold, distributed, or had made, Modifications made by that
+         Participant.
+
+         8.3.  If You assert a patent infringement claim against Participant
+         alleging that such Participant's Contributor Version directly or
+         indirectly infringes any patent where such claim is resolved (such as
+         by license or settlement) prior to the initiation of patent
+         infringement litigation, then the reasonable value of the licenses
+         granted by such Participant under Sections 2.1 or 2.2 shall be taken
+         into account in determining the amount or value of any payment or
+         license.
+
+         8.4.  In the event of termination under Sections 8.1 or 8.2 above,
+         all end user license agreements (excluding distributors and resellers)
+         which have been validly granted by You or any distributor hereunder
+         prior to termination shall survive termination.
+
+    9. LIMITATION OF LIABILITY.
+
+         UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+         (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL
+         DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE,
+         OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR
+         ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY
+         CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL,
+         WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
+         COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
+         INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+         LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
+         RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+         PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE
+         EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO
+         THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
+
+    10. U.S. GOVERNMENT END USERS.
+
+         The Covered Code is a "commercial item," as that term is defined in
+         48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer
+         software" and "commercial computer software documentation," as such
+         terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48
+         C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995),
+         all U.S. Government End Users acquire Covered Code with only those
+         rights set forth herein.
+
+    11. MISCELLANEOUS.
+
+         This License represents the complete agreement concerning subject
+         matter hereof. If any provision of this License is held to be
+         unenforceable, such provision shall be reformed only to the extent
+         necessary to make it enforceable. This License shall be governed by
+         California law provisions (except to the extent applicable law, if
+         any, provides otherwise), excluding its conflict-of-law provisions.
+         With respect to disputes in which at least one party is a citizen of,
+         or an entity chartered or registered to do business in the United
+         States of America, any litigation relating to this License shall be
+         subject to the jurisdiction of the Federal Courts of the Northern
+         District of California, with venue lying in Santa Clara County,
+         California, with the losing party responsible for costs, including
+         without limitation, court costs and reasonable attorneys' fees and
+         expenses. The application of the United Nations Convention on
+         Contracts for the International Sale of Goods is expressly excluded.
+         Any law or regulation which provides that the language of a contract
+         shall be construed against the drafter shall not apply to this
+         License.
+
+    12. RESPONSIBILITY FOR CLAIMS.
+
+         As between Initial Developer and the Contributors, each party is
+         responsible for claims and damages arising, directly or indirectly,
+         out of its utilization of rights under this License and You agree to
+         work with Initial Developer and Contributors to distribute such
+         responsibility on an equitable basis. Nothing herein is intended or
+         shall be deemed to constitute any admission of liability.
+
+    13. MULTIPLE-LICENSED CODE.
+
+         Initial Developer may designate portions of the Covered Code as
+         "Multiple-Licensed".  "Multiple-Licensed" means that the Initial
+         Developer permits you to utilize portions of the Covered Code under
+         Your choice of the NPL or the alternative licenses, if any, specified
+         by the Initial Developer in the file described in Exhibit A.
+
+    EXHIBIT A -Mozilla Public License.
+
+         ``The contents of this file are subject to the Mozilla Public License
+         Version 1.1 (the "License"); you may not use this file except in
+         compliance with the License. You may obtain a copy of the License at
+         http://www.mozilla.org/MPL/
+
+         Software distributed under the License is distributed on an "AS IS"
+         basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+         License for the specific language governing rights and limitations
+         under the License.
+
+         The Original Code is ______________________________________.
+
+         The Initial Developer of the Original Code is ________________________.
+         Portions created by ______________________ are Copyright (C) ______
+         _______________________. All Rights Reserved.
+
+         Contributor(s): ______________________________________.
+
+         Alternatively, the contents of this file may be used under the terms
+         of the _____ license (the  "[___] License"), in which case the
+         provisions of [______] License are applicable instead of those
+         above.  If you wish to allow use of your version of this file only
+         under the terms of the [____] License and not to allow others to use
+         your version of this file under the MPL, indicate your decision by
+         deleting  the provisions above and replace  them with the notice and
+         other provisions required by the [___] License.  If you do not delete
+         the provisions above, a recipient may use your version of this file
+         under either the MPL or the [___] License."
+
+         [NOTE: The text of this Exhibit A may differ slightly from the text of
+         the notices in the Source Code files of the Original Code. You should
+         use the text of this Exhibit A rather than the text found in the
+         Original Code Source Code for Your Modifications.]
diff --git a/jackrabbit-jca/src/test/java/org/apache/jackrabbit/jca/test/ConnectionFactoryTest.java b/jackrabbit-jca/src/test/java/org/apache/jackrabbit/jca/test/ConnectionFactoryTest.java
index 13751ef..d0d2fcf 100644
--- a/jackrabbit-jca/src/test/java/org/apache/jackrabbit/jca/test/ConnectionFactoryTest.java
+++ b/jackrabbit-jca/src/test/java/org/apache/jackrabbit/jca/test/ConnectionFactoryTest.java
@@ -67,6 +67,9 @@ public final class ConnectionFactoryTest
         // Check if not same
         assertNotSame(cri1, cri2);
 
+        // Create the connection factory
+        mcf.createConnectionFactory();
+
         // Allocate connections
         ManagedConnection mc1 = mcf.createManagedConnection(null, cri1);
         ManagedConnection mc2 = mcf.createManagedConnection(null, cri2);
diff --git a/jackrabbit-jcr-client/pom.xml b/jackrabbit-jcr-client/pom.xml
index fa9cbf2..254b9c1 100644
--- a/jackrabbit-jcr-client/pom.xml
+++ b/jackrabbit-jcr-client/pom.xml
@@ -26,7 +26,7 @@
     <parent>
         <groupId>org.apache.jackrabbit</groupId>
         <artifactId>jackrabbit-parent</artifactId>
-        <version>2.3.6</version>
+        <version>2.10.1</version>
         <relativePath>../jackrabbit-parent/pom.xml</relativePath>
     </parent>
     <artifactId>jackrabbit-jcr-client</artifactId>
@@ -91,37 +91,37 @@
         <dependency>
             <groupId>org.apache.jackrabbit</groupId>
             <artifactId>jackrabbit-jcr2spi</artifactId>
-            <version>2.3.6</version>
+            <version>${project.version}</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.jackrabbit</groupId>
             <artifactId>jackrabbit-spi2dav</artifactId>
-            <version>2.3.6</version>
+            <version>${project.version}</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.jackrabbit</groupId>
             <artifactId>jackrabbit-spi2jcr</artifactId>
-            <version>2.3.6</version>
+            <version>${project.version}</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.jackrabbit</groupId>
             <artifactId>jackrabbit-core</artifactId>
-            <version>2.3.6</version>
+            <version>${project.version}</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.jackrabbit</groupId>
             <artifactId>jackrabbit-spi-commons</artifactId>
-            <version>2.3.6</version>
+            <version>${project.version}</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.jackrabbit</groupId>
             <artifactId>jackrabbit-spi</artifactId>
-            <version>2.3.6</version>
+            <version>${project.version}</version>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git a/jackrabbit-jcr-client/src/test/java/org/apache/jackrabbit/client/RepositoryFactoryImplTest.java b/jackrabbit-jcr-client/src/test/java/org/apache/jackrabbit/client/RepositoryFactoryImplTest.java
index 8037404..1d6f414 100644
--- a/jackrabbit-jcr-client/src/test/java/org/apache/jackrabbit/client/RepositoryFactoryImplTest.java
+++ b/jackrabbit-jcr-client/src/test/java/org/apache/jackrabbit/client/RepositoryFactoryImplTest.java
@@ -68,6 +68,7 @@ import org.apache.jackrabbit.spi.NodeId;
 import org.apache.jackrabbit.spi.NodeInfo;
 import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.PathFactory;
+import org.apache.jackrabbit.spi.PrivilegeDefinition;
 import org.apache.jackrabbit.spi.PropertyId;
 import org.apache.jackrabbit.spi.PropertyInfo;
 import org.apache.jackrabbit.spi.QNodeDefinition;
@@ -80,6 +81,7 @@ import org.apache.jackrabbit.spi.RepositoryService;
 import org.apache.jackrabbit.spi.RepositoryServiceFactory;
 import org.apache.jackrabbit.spi.SessionInfo;
 import org.apache.jackrabbit.spi.Subscription;
+import org.apache.jackrabbit.spi.Tree;
 import org.apache.jackrabbit.spi.commons.logging.Slf4jLogWriterProvider;
 import org.apache.jackrabbit.webdav.DavException;
 
@@ -224,6 +226,11 @@ public class RepositoryFactoryImplTest extends TestCase {
             return 1234;
         }
 
+        @Override
+        public <T> T getConfiguration(String name, T defaultValue) {
+            return null;
+        }
+
         public RepositoryService getRepositoryService() throws RepositoryException {
             return RepositoryServiceImpl.INSTANCE;
         }
@@ -289,6 +296,21 @@ public class RepositoryFactoryImplTest extends TestCase {
             return false;
         }
 
+        @Override
+        public PrivilegeDefinition[] getPrivilegeDefinitions(SessionInfo sessionInfo) throws RepositoryException {
+            return new PrivilegeDefinition[0];
+        }
+
+        @Override
+        public Name[] getPrivilegeNames(SessionInfo sessionInfo, NodeId id) throws RepositoryException {
+            return new Name[0];
+        }
+
+        @Override
+        public PrivilegeDefinition[] getSupportedPrivileges(SessionInfo sessionInfo, NodeId nodeId) throws RepositoryException {
+            return new PrivilegeDefinition[0];
+        }
+
         public QNodeDefinition getNodeDefinition(SessionInfo sessionInfo, NodeId nodeId) throws RepositoryException {
             return null;
         }
@@ -325,6 +347,11 @@ public class RepositoryFactoryImplTest extends TestCase {
             // empty
         }
 
+        @Override
+        public Tree createTree(SessionInfo sessionInfo, Batch batch, Name nodeName, Name primaryTypeName, String uniqueId) throws RepositoryException {
+            return null;
+        }
+
         public void importXml(SessionInfo sessionInfo, NodeId parentId, InputStream xmlStream, int uuidBehaviour) throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException, AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
             // empty
         }
diff --git a/jackrabbit-jcr-commons/pom.xml b/jackrabbit-jcr-commons/pom.xml
index 614cb79..83e9e7b 100644
--- a/jackrabbit-jcr-commons/pom.xml
+++ b/jackrabbit-jcr-commons/pom.xml
@@ -26,7 +26,7 @@
   <parent>
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>jackrabbit-parent</artifactId>
-    <version>2.3.6</version>
+    <version>2.10.1</version>
     <relativePath>../jackrabbit-parent/pom.xml</relativePath>
   </parent>
   <artifactId>jackrabbit-jcr-commons</artifactId>
@@ -95,7 +95,7 @@
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-api</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <optional>true</optional>
     </dependency>
     <dependency>
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/AbstractRepository.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/AbstractRepository.java
index 05a0043..5cfe096 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/AbstractRepository.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/AbstractRepository.java
@@ -17,9 +17,12 @@
 package org.apache.jackrabbit.commons;
 
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 
 import javax.jcr.Credentials;
+import javax.jcr.LoginException;
+import javax.jcr.NoSuchWorkspaceException;
 import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
@@ -101,6 +104,24 @@ public abstract class AbstractRepository implements Repository {
     }
 
     /**
+     * This implementation directly delegates to {@link #login(javax.jcr.Credentials, String)}
+     * not supporting any attributes.
+     *
+     * @param credentials the credentials of the user
+     * @param workspaceName the name of a workspace
+     * @param attributes implementation-specific session attributes
+     * @return a valid session for the user to access the repository.
+     * @throws javax.jcr.LoginException
+     * @throws javax.jcr.NoSuchWorkspaceException
+     * @throws RepositoryException
+     */
+    public Session login(
+            Credentials credentials, String workspaceName, Map<String, Object> attributes)
+            throws LoginException, NoSuchWorkspaceException, RepositoryException {
+        return login(credentials, workspaceName);
+    }
+
+    /**
      * Calls {@link Repository#login(Credentials, String)} with
      * <code>null</code> arguments.
      *
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/AbstractSession.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/AbstractSession.java
index e8554ee..7785171 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/AbstractSession.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/AbstractSession.java
@@ -374,26 +374,32 @@ public abstract class AbstractSession implements Session {
      * <p>
      * The default implementation:
      * <ul>
-     * <li>Throws a {@link PathNotFoundException} if the given path
-     *     does not start with a slash.
      * <li>Returns the root node if the given path is "/"
-     * <li>Calls {@link Node#getNode(String)} on the root node with the
-     *     part of the given path after the first slash
-     * <li>Calls {@link Node#getProperty(String)} similarly in case the
-     *     above call fails with a {@link PathNotFoundException}
+     * <li>Delegates to {@link Node#getNodeByIdentifier(String)} for identifier
+     * paths
+     * <li>Throws a {@link PathNotFoundException} if the given path does not
+     * start with a slash.
+     * <li>Calls {@link Node#getNode(String)} on the root node with the part of
+     * the given path after the first slash
+     * <li>Calls {@link Node#getProperty(String)} similarly in case the above
+     * call fails with a {@link PathNotFoundException}
      * </ul>
-     *
+     * 
      * @see Session#getItem(String)
-     * @param absPath absolute path
+     * @param absPath
+     *            absolute path
      * @return the node or property with the given path
-     * @throws PathNotFoundException if the given path is invalid or not found
-     * @throws RepositoryException if another error occurs
+     * @throws PathNotFoundException
+     *             if the given path is invalid or not found
+     * @throws RepositoryException
+     *             if another error occurs
      */
-    public Item getItem(String absPath)
-            throws PathNotFoundException, RepositoryException {
+    public Item getItem(String absPath) throws PathNotFoundException, RepositoryException {
         Node root = getRootNode();
         if (absPath.equals("/")) {
             return root;
+        } else if (absPath.startsWith("[") && absPath.endsWith("]")) {
+            return getNodeByIdentifier(absPath.substring(1, absPath.length() - 1));
         } else {
             String relPath = toRelativePath(absPath);
             if (root.hasNode(relPath)) {
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/ItemNameMatcher.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/ItemNameMatcher.java
new file mode 100644
index 0000000..5994ce1
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/ItemNameMatcher.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.commons;
+
+import java.util.StringTokenizer;
+
+/**
+ * Utility for name matching such as required for {@link javax.jcr.Node#getNodes(String)},
+ * {@link javax.jcr.Node#getNodes(String[])}, {@link javax.jcr.Node#getProperties(String])}
+ * and {@link javax.jcr.Node#getProperties(String[])}.
+ */
+public final class ItemNameMatcher {
+
+    private static final char WILDCARD_CHAR = '*';
+    private static final String OR = "|";
+
+    private ItemNameMatcher() { }
+
+    /**
+     * Matches the name pattern against the specified name.
+     * <p>
+     * The pattern may be a full name or a partial name with one or more
+     * wildcard characters ("*"), or a disjunction (using the "|" character
+     * to represent logical <i>OR</i>) of these. For example,
+     * <p>
+     * {@code "jcr:*|foo:bar"}
+     * <p>
+     * would match
+     * <p>
+     * {@code "foo:bar"}, but also {@code "jcr:whatever"}.
+     * <p>
+     * <pre>
+     * The EBNF for pattern is:
+     *
+     * namePattern ::= disjunct {'|' disjunct}
+     * disjunct ::= name [':' name]
+     * name ::= '*' |
+     *          ['*'] fragment {'*' fragment}['*']
+     * fragment ::= char {char}
+     * char ::= nonspace | ' '
+     * nonspace ::= (* Any Unicode character except:
+     *               '/', ':', '[', ']', '*',
+     *               ''', '"', '|' or any whitespace
+     *               character *)
+     * </pre>
+     * Note that leading and trailing whitespace around a pattern <i>is</i> ignored.
+     *
+     * @param name the name to test the pattern with
+     * @param pattern the pattern to be matched against the name
+     * @return true if the specified name matches the pattern
+     * @see javax.jcr.Node#getNodes(String)
+     */
+    public static boolean matches(String name, String pattern) {
+        StringTokenizer st = new StringTokenizer(pattern, OR, false);
+        while (st.hasMoreTokens()) {
+            // remove leading & trailing whitespace from token
+            String token = st.nextToken().trim();
+            if (internalMatches(name, token, 0, 0)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Matches the {@code nameGlob} strings in the passed array against
+     * the specified name.
+     * <p>
+     * A glob may be a full name or a partial name with one or more
+     * wildcard characters ("{@code *}").
+     * <p>
+     * Note that unlike in the case of the {@link #matches(String, String)}
+     * leading and trailing whitespace around a glob is <i>not</i> ignored.
+     *
+     * @param name the name to test the pattern with
+     * @param nameGlobs an array of globbing strings
+     * @return true if the specified name matches any of the globs
+     * @see javax.jcr.Node#getNodes(String[])
+     */
+    public static boolean matches(String name, String[] nameGlobs) {
+        for (String nameGlob : nameGlobs) {
+            // use globbing string as-is. Don't trim any leading/trailing whitespace
+            if (internalMatches(name, nameGlob, 0, 0)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    //------------------------------------------< private >---
+
+    /**
+     * Internal helper used to recursively match the pattern
+     *
+     * @param s       The string to be tested
+     * @param pattern The pattern
+     * @param sOff    offset within {@code s}
+     * @param pOff    offset within {@code pattern}.
+     * @return true if {@code s} matched pattern, else false.
+     */
+    private static boolean internalMatches(String s, String pattern, int sOff, int pOff) {
+        int pLen = pattern.length();
+        int sLen = s.length();
+
+        while (true) {
+            if (pOff >= pLen) {
+                if (sOff >= sLen) {
+                    return true;
+                } else if (s.charAt(sOff) == '[') {
+                    // check for subscript notation (e.g. "whatever[1]")
+                    // the entire pattern matched up to the subscript:
+                    // -> ignore the subscript
+                    return true;
+                } else {
+                    return false;
+                }
+            }
+            if (sOff >= sLen && pattern.charAt(pOff) != WILDCARD_CHAR) {
+                return false;
+            }
+
+            // check for a '*' as the next pattern char; this is handled by
+            // a recursive call for each postfix of the name.
+            if (pattern.charAt(pOff) == WILDCARD_CHAR) {
+                if (++pOff >= pLen) {
+                    return true;
+                }
+
+                while (true) {
+                    if (internalMatches(s, pattern, sOff, pOff)) {
+                        return true;
+                    }
+                    if (sOff >= sLen) {
+                        return false;
+                    }
+                    sOff++;
+                }
+            }
+
+            if (pOff < pLen && sOff < sLen) {
+                if (pattern.charAt(pOff) != s.charAt(sOff)) {
+                    return false;
+                }
+            }
+            pOff++;
+            sOff++;
+        }
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/JcrUtils.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/JcrUtils.java
index 6286bf1..ad99be0 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/JcrUtils.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/JcrUtils.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
+import java.math.BigDecimal;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
@@ -38,6 +39,7 @@ import javax.jcr.Binary;
 import javax.jcr.Item;
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
+import javax.jcr.PathNotFoundException;
 import javax.jcr.Property;
 import javax.jcr.PropertyIterator;
 import javax.jcr.PropertyType;
@@ -47,13 +49,17 @@ import javax.jcr.RepositoryFactory;
 import javax.jcr.Session;
 import javax.jcr.Value;
 import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.NodeTypeIterator;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.EventListener;
+import javax.jcr.observation.EventListenerIterator;
 import javax.jcr.query.QueryResult;
 import javax.jcr.query.Row;
 import javax.jcr.query.RowIterator;
-
-import org.apache.jackrabbit.commons.iterator.NodeIterable;
-import org.apache.jackrabbit.commons.iterator.PropertyIterable;
-import org.apache.jackrabbit.commons.iterator.RowIterable;
+import javax.jcr.security.AccessControlPolicyIterator;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionIterator;
 
 /**
  * Collection of static utility methods for use with the JCR API.
@@ -258,217 +264,650 @@ public class JcrUtils {
     }
 
     /**
-     * Calls {@link Node#getSharedSet()} on the given node and returns
-     * the resulting {@link NodeIterator} as an {@link Iterable<Node>} instance
-     * for use in a Java 5 for-each loop.
+     * Returns an {@link Iterable<Node>} over the shared set of the given node.
+     * <p>
+     * The first iterator is acquired directly during this method call to
+     * allow a possible {@link RepositoryException} to be thrown as-is.
+     * Further iterators are acquired lazily when needed, with possible
+     * {@link RepositoryException}s wrapped into {@link RuntimeException}s.
      *
-     * @see NodeIterable
+     * @see Node#getSharedSet()
      * @param node shared node
      * @return nodes in the shared set
      * @throws RepositoryException if the {@link Node#getSharedSet()} call fails
      */
-    public static Iterable<Node> getSharedSet(Node node)
+    public static Iterable<Node> getSharedSet(final Node node)
             throws RepositoryException {
-        return new NodeIterable(node.getSharedSet());
+        final NodeIterator iterator = node.getSharedSet();
+        return new Iterable<Node>() {
+            private boolean first = true;
+            @Override @SuppressWarnings("unchecked")
+            public synchronized Iterator<Node> iterator() {
+                if (first) {
+                    first = false;
+                    return iterator;
+                } else {
+                    try {
+                        return node.getSharedSet();
+                    } catch (RepositoryException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        };
     }
 
     /**
-     * Calls {@link Node#getNodes()} on the given node and returns the
-     * resulting {@link NodeIterator} as an {@link Iterable<Node>} instance
-     * for use in a Java 5 for-each loop.
+     * Returns an {@link Iterable<Node>} over the children of the given node.
+     * <p>
+     * The first iterator is acquired directly during this method call to
+     * allow a possible {@link RepositoryException} to be thrown as-is.
+     * Further iterators are acquired lazily when needed, with possible
+     * {@link RepositoryException}s wrapped into {@link RuntimeException}s.
      *
-     * @see NodeIterable
+     * @see Node#getNodes()
      * @param node parent node
      * @return child nodes
      * @throws RepositoryException if the {@link Node#getNodes()} call fails
      */
-    public static Iterable<Node> getChildNodes(Node node)
+    public static Iterable<Node> getChildNodes(final Node node)
             throws RepositoryException {
-        return new NodeIterable(node.getNodes());
+        final NodeIterator iterator = node.getNodes();
+        return new Iterable<Node>() {
+            private boolean first = true;
+            @Override @SuppressWarnings("unchecked")
+            public synchronized Iterator<Node> iterator() {
+                if (first) {
+                    first = false;
+                    return iterator;
+                } else {
+                    try {
+                        return node.getNodes();
+                    } catch (RepositoryException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        };
     }
 
     /**
-     * Calls {@link Node#getNodes(String)} on the given node with the given
-     * name pattern and returns the resulting {@link NodeIterator} as an
-     * {@link Iterable<Node>} instance for use in a Java 5 for-each loop.
+     * Returns an {@link Iterable<Node>} over those children of the given node
+     * that match the given name pattern.
+     * <p>
+     * The first iterator is acquired directly during this method call to
+     * allow a possible {@link RepositoryException} to be thrown as-is.
+     * Further iterators are acquired lazily when needed, with possible
+     * {@link RepositoryException}s wrapped into {@link RuntimeException}s.
      *
-     * @see NodeIterable
+     * @see Node#getNodes(String)
      * @param node parent node
      * @param pattern node name pattern
      * @return matching child nodes
      * @throws RepositoryException
      *         if the {@link Node#getNodes(String)} call fails
      */
-    public static Iterable<Node> getChildNodes(Node node, String pattern)
-            throws RepositoryException {
-        return new NodeIterable(node.getNodes(pattern));
+    public static Iterable<Node> getChildNodes(
+            final Node node, final String pattern) throws RepositoryException {
+        final NodeIterator iterator = node.getNodes(pattern);
+        return new Iterable<Node>() {
+            private boolean first = true;
+            @Override @SuppressWarnings("unchecked")
+            public synchronized Iterator<Node> iterator() {
+                if (first) {
+                    first = false;
+                    return iterator;
+                } else {
+                    try {
+                        return node.getNodes(pattern);
+                    } catch (RepositoryException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        };
     }
 
     /**
-     * Calls {@link Node#getNodes(String[])} on the given node with the given
-     * name globs and returns the resulting {@link NodeIterator} as an
-     * {@link Iterable<Node>} instance for use in a Java 5 for-each loop.
+     * Returns an {@link Iterable<Node>} over those children of the given node
+     * that match the given name patterns.
+     * <p>
+     * The first iterator is acquired directly during this method call to
+     * allow a possible {@link RepositoryException} to be thrown as-is.
+     * Further iterators are acquired lazily when needed, with possible
+     * {@link RepositoryException}s wrapped into {@link RuntimeException}s.
      *
-     * @see NodeIterable
+     * @see Node#getNodes(String[])
      * @param node parent node
      * @param globs node name pattern
      * @return matching child nodes
      * @throws RepositoryException
      *         if the {@link Node#getNodes(String[])} call fails
      */
-    public static Iterable<Node> getChildNodes(Node node, String[] globs)
-            throws RepositoryException {
-        return new NodeIterable(node.getNodes(globs));
+    public static Iterable<Node> getChildNodes(
+            final Node node, final String[] globs) throws RepositoryException {
+        final NodeIterator iterator = node.getNodes(globs);
+        return new Iterable<Node>() {
+            private boolean first = true;
+            @Override @SuppressWarnings("unchecked")
+            public synchronized Iterator<Node> iterator() {
+                if (first) {
+                    first = false;
+                    return iterator;
+                } else {
+                    try {
+                        return node.getNodes(globs);
+                    } catch (RepositoryException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        };
     }
 
     /**
-     * Calls {@link Node#getProperties()} on the given node and returns the
-     * resulting {@link NodeIterator} as an {@link Iterable<Node>} instance
-     * for use in a Java 5 for-each loop.
+     * Returns an {@link Iterable<Node>} over the properties of the given node.
+     * <p>
+     * The first iterator is acquired directly during this method call to
+     * allow a possible {@link RepositoryException} to be thrown as-is.
+     * Further iterators are acquired lazily when needed, with possible
+     * {@link RepositoryException}s wrapped into {@link RuntimeException}s.
      *
-     * @see PropertyIterable
+     * @see Node#getProperties()
      * @param node node
      * @return properties of the node
      * @throws RepositoryException
      *         if the {@link Node#getProperties()} call fails
      */
-    public static Iterable<Property> getProperties(Node node)
+    public static Iterable<Property> getProperties(final Node node)
             throws RepositoryException {
-        return new PropertyIterable(node.getProperties());
+        final PropertyIterator iterator = node.getProperties();
+        return new Iterable<Property>() {
+            private boolean first = true;
+            @Override @SuppressWarnings("unchecked")
+            public synchronized Iterator<Property> iterator() {
+                if (first) {
+                    first = false;
+                    return iterator;
+                } else {
+                    try {
+                        return node.getProperties();
+                    } catch (RepositoryException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        };
     }
 
     /**
-     * Calls {@link Node#getProperties(String)} on the given node with the
-     * given name pattern and returns the resulting {@link PropertyIterator}
-     * as an {@link Iterable<Property>} instance for use in a Java 5
-     * for-each loop.
+     * Returns an {@link Iterable<Node>} over those properties of the
+     * given node that match the given name pattern.
+     * <p>
+     * The first iterator is acquired directly during this method call to
+     * allow a possible {@link RepositoryException} to be thrown as-is.
+     * Further iterators are acquired lazily when needed, with possible
+     * {@link RepositoryException}s wrapped into {@link RuntimeException}s.
      *
-     * @see PropertyIterable
+     * @see Node#getProperties(String)
      * @param node node
      * @param pattern property name pattern
      * @return matching properties of the node
      * @throws RepositoryException
      *         if the {@link Node#getProperties(String)} call fails
      */
-    public static Iterable<Property> getProperties(Node node, String pattern)
-            throws RepositoryException {
-        return new PropertyIterable(node.getProperties(pattern));
+    public static Iterable<Property> getProperties(
+            final Node node, final String pattern) throws RepositoryException {
+        final PropertyIterator iterator = node.getProperties(pattern);
+        return new Iterable<Property>() {
+            private boolean first = true;
+            @Override @SuppressWarnings("unchecked")
+            public synchronized Iterator<Property> iterator() {
+                if (first) {
+                    first = false;
+                    return iterator;
+                } else {
+                    try {
+                        return node.getProperties(pattern);
+                    } catch (RepositoryException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        };
     }
 
     /**
-     * Calls {@link Node#getProperties(String[])} on the given node with the
-     * given name globs and returns the resulting {@link PropertyIterator}
-     * as an {@link Iterable<Property>} instance for use in a Java 5
-     * for-each loop.
+     * Returns an {@link Iterable<Node>} over those properties of the
+     * given node that match the given name patterns.
+     * <p>
+     * The first iterator is acquired directly during this method call to
+     * allow a possible {@link RepositoryException} to be thrown as-is.
+     * Further iterators are acquired lazily when needed, with possible
+     * {@link RepositoryException}s wrapped into {@link RuntimeException}s.
      *
-     * @see PropertyIterable
+     * @see Node#getProperties(String[])
      * @param node node
      * @param globs property name globs
      * @return matching properties of the node
      * @throws RepositoryException
      *         if the {@link Node#getProperties(String[])} call fails
      */
-    public static Iterable<Property> getProperties(Node node, String[] globs)
-            throws RepositoryException {
-        return new PropertyIterable(node.getProperties(globs));
+    public static Iterable<Property> getProperties(
+            final Node node, final String[] globs) throws RepositoryException {
+        final PropertyIterator iterator = node.getProperties(globs);
+        return new Iterable<Property>() {
+            private boolean first = true;
+            @Override @SuppressWarnings("unchecked")
+            public synchronized Iterator<Property> iterator() {
+                if (first) {
+                    first = false;
+                    return iterator;
+                } else {
+                    try {
+                        return node.getProperties(globs);
+                    } catch (RepositoryException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        };
     }
 
     /**
-     * Calls {@link Node#getReferences()} on the given node and returns the
-     * resulting {@link PropertyIterator} as an {@link Iterable<Property>}
-     * instance for use in a Java 5 for-each loop.
+     * Returns an {@link Iterable<Node>} over references to the given node.
+     * <p>
+     * The first iterator is acquired directly during this method call to
+     * allow a possible {@link RepositoryException} to be thrown as-is.
+     * Further iterators are acquired lazily when needed, with possible
+     * {@link RepositoryException}s wrapped into {@link RuntimeException}s.
      *
-     * @see PropertyIterable
+     * @see Node#getReferences()
      * @param node reference target
      * @return references that point to the given node
      * @throws RepositoryException
      *         if the {@link Node#getReferences()} call fails
      */
-    public static Iterable<Property> getReferences(Node node)
+    public static Iterable<Property> getReferences(final Node node)
             throws RepositoryException {
-        return new PropertyIterable(node.getReferences());
+        final PropertyIterator iterator = node.getReferences();
+        return new Iterable<Property>() {
+            private boolean first = true;
+            @Override @SuppressWarnings("unchecked")
+            public synchronized Iterator<Property> iterator() {
+                if (first) {
+                    first = false;
+                    return iterator;
+                } else {
+                    try {
+                        return node.getReferences();
+                    } catch (RepositoryException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        };
     }
 
     /**
-     * Calls {@link Node#getReferences(String)} on the given node and returns
-     * the resulting {@link PropertyIterator} as an {@link Iterable<Property>}
-     * instance for use in a Java 5 for-each loop.
+     * Returns an {@link Iterable<Node>} over those references to the
+     * given node that have the given name.
+     * <p>
+     * The first iterator is acquired directly during this method call to
+     * allow a possible {@link RepositoryException} to be thrown as-is.
+     * Further iterators are acquired lazily when needed, with possible
+     * {@link RepositoryException}s wrapped into {@link RuntimeException}s.
      *
-     * @see PropertyIterable
+     * @see Node#getReferences(String)
      * @param node reference target
      * @param name reference property name
      * @return references with the given name that point to the given node
      * @throws RepositoryException
      *         if the {@link Node#getReferences(String)} call fails
      */
-    public static Iterable<Property> getReferences(Node node, String name)
-            throws RepositoryException {
-        return new PropertyIterable(node.getReferences(name));
+    public static Iterable<Property> getReferences(
+            final Node node, final String name) throws RepositoryException {
+        final PropertyIterator iterator = node.getReferences(name);
+        return new Iterable<Property>() {
+            private boolean first = true;
+            @Override @SuppressWarnings("unchecked")
+            public synchronized Iterator<Property> iterator() {
+                if (first) {
+                    first = false;
+                    return iterator;
+                } else {
+                    try {
+                        return node.getReferences(name);
+                    } catch (RepositoryException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        };
     }
 
     /**
-     * Calls {@link Node#getWeakReferences()} on the given node and returns the
-     * resulting {@link PropertyIterator} as an {@link Iterable<Property>}
-     * instance for use in a Java 5 for-each loop.
+     * Returns an {@link Iterable<Node>} over weak references to the given node.
+     * <p>
+     * The first iterator is acquired directly during this method call to
+     * allow a possible {@link RepositoryException} to be thrown as-is.
+     * Further iterators are acquired lazily when needed, with possible
+     * {@link RepositoryException}s wrapped into {@link RuntimeException}s.
      *
-     * @see PropertyIterable
+     * @see Node#getWeakReferences()
      * @param node reference target
      * @return weak references that point to the given node
      * @throws RepositoryException
      *         if the {@link Node#getWeakReferences()} call fails
      */
-    public static Iterable<Property> getWeakReferences(Node node)
+    public static Iterable<Property> getWeakReferences(final Node node)
             throws RepositoryException {
-        return new PropertyIterable(node.getWeakReferences());
+        final PropertyIterator iterator = node.getWeakReferences();
+        return new Iterable<Property>() {
+            private boolean first = true;
+            @Override @SuppressWarnings("unchecked")
+            public synchronized Iterator<Property> iterator() {
+                if (first) {
+                    first = false;
+                    return iterator;
+                } else {
+                    try {
+                        return node.getWeakReferences();
+                    } catch (RepositoryException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        };
     }
 
     /**
-     * Calls {@link Node#getReferences(String)} on the given node and returns
-     * the resulting {@link PropertyIterator} as an {@link Iterable<Property>}
-     * instance for use in a Java 5 for-each loop.
+     * Returns an {@link Iterable<Node>} over those weak references to the
+     * given node that have the given name.
+     * <p>
+     * The first iterator is acquired directly during this method call to
+     * allow a possible {@link RepositoryException} to be thrown as-is.
+     * Further iterators are acquired lazily when needed, with possible
+     * {@link RepositoryException}s wrapped into {@link RuntimeException}s.
      *
-     * @see PropertyIterable
+     * @see Node#getWeakReferences(String)
      * @param node reference target
      * @param name reference property name
      * @return weak references with the given name that point to the given node
      * @throws RepositoryException
      *         if the {@link Node#getWeakReferences(String)} call fails
      */
-    public static Iterable<Property> getWeakReferences(Node node, String name)
-            throws RepositoryException {
-        return new PropertyIterable(node.getWeakReferences(name));
+    public static Iterable<Property> getWeakReferences(
+            final Node node, final String name) throws RepositoryException {
+        final PropertyIterator iterator = node.getWeakReferences(name);
+        return new Iterable<Property>() {
+            private boolean first = true;
+            @Override @SuppressWarnings("unchecked")
+            public synchronized Iterator<Property> iterator() {
+                if (first) {
+                    first = false;
+                    return iterator;
+                } else {
+                    try {
+                        return node.getWeakReferences(name);
+                    } catch (RepositoryException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        };
     }
 
     /**
-     * Calls {@link QueryResult#getNodes()} on the given query result and
-     * returns the resulting {@link NodeIterator} as an {@link Iterable<Node>}
-     * instance for use in a Java 5 for-each loop.
+     * Returns an {@link Iterable<Node>} over nodes in the given query result.
+     * <p>
+     * The first iterator is acquired directly during this method call to
+     * allow a possible {@link RepositoryException} to be thrown as-is.
+     * Further iterators are acquired lazily when needed, with possible
+     * {@link RepositoryException}s wrapped into {@link RuntimeException}s.
      *
-     * @see NodeIterable
+     * @see QueryResult#getNodes()
      * @param result query result
      * @return nodes in the query result
      * @throws RepositoryException
      *         if the {@link QueryResult#getNodes()} call fails
      */
-    public static Iterable<Node> getNodes(QueryResult result)
+    public static Iterable<Node> getNodes(final QueryResult result)
             throws RepositoryException {
-        return new NodeIterable(result.getNodes());
+        final NodeIterator iterator = result.getNodes();
+        return new Iterable<Node>() {
+            private boolean first = true;
+            @Override @SuppressWarnings("unchecked")
+            public synchronized Iterator<Node> iterator() {
+                if (first) {
+                    first = false;
+                    return iterator;
+                } else {
+                    try {
+                        return result.getNodes();
+                    } catch (RepositoryException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        };
     }
 
     /**
-     * Calls {@link QueryResult#getRows()} on the given query result and
-     * returns the resulting {@link RowIterator} as an {@link Iterable<Row>}
-     * instance for use in a Java 5 for-each loop.
+     * Returns an {@link Iterable<Row>} over nodes in the given query result.
+     * <p>
+     * The first iterator is acquired directly during this method call to
+     * allow a possible {@link RepositoryException} to be thrown as-is.
+     * Further iterators are acquired lazily when needed, with possible
+     * {@link RepositoryException}s wrapped into {@link RuntimeException}s.
      *
-     * @see RowIterable
+     * @see QueryResult#getRows()
      * @param result query result
      * @return rows in the query result
      * @throws RepositoryException
      *         if the {@link QueryResult#getRows()} call fails
      */
-    public static Iterable<Row> getRows(QueryResult result)
+    public static Iterable<Row> getRows(final QueryResult result)
             throws RepositoryException {
-        return new RowIterable(result.getRows());
+        final RowIterator iterator = result.getRows();
+        return new Iterable<Row>() {
+            private boolean first = true;
+            @Override @SuppressWarnings("unchecked")
+            public synchronized Iterator<Row> iterator() {
+                if (first) {
+                    first = false;
+                    return iterator;
+                } else {
+                    try {
+                        return result.getRows();
+                    } catch (RepositoryException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        };
+    }
+
+    /**
+     * Transform any type of {@link Iterator} into an {@link Iterable}
+     * <strong>for single use</strong> in a Java 5 for-each loop.
+     * <p>
+     * <strong>While general purpose <code>Iterables</code> tend to be reusable,
+     * this wrapper <code>Iterable</code> consumes the argument
+     * <code>Iterator</code>, leaving it in a non-reusable state. The returned
+     * <code>Iterable</code> will throw an <code>IllegalStateException</code> if
+     * its <code>iterator()</code> method is invoked a second time.</strong>
+     *
+     * @param iterator
+     *            The input <code>Iterator</code>
+     * @return The wrapping <code>Iterable</code>
+     */
+    public static <I> Iterable<I> in(final Iterator<I> iterator) {
+        return new Iterable<I>() {
+            private boolean stale = false;
+
+            @Override
+            public synchronized Iterator<I> iterator() {
+                if (stale) {
+                    throw new IllegalStateException("Cannot reuse Iterable intended for single use");
+                }
+
+                stale = true;
+                return iterator;
+            }
+        };
+    }
+
+    /**
+     * Transform an {@link AccessControlPolicyIterator} into an {@link Iterable}
+     * <strong>for single use</strong> in a Java 5 for-each loop.
+     * <p>
+     * <strong>While general purpose <code>Iterables</code> tend to be reusable,
+     * this wrapper <code>Iterable</code> consumes the argument
+     * <code>Iterator</code>, leaving it in a non-reusable state. The returned
+     * <code>Iterable</code> will throw an <code>IllegalStateException</code> if
+     * its <code>iterator()</code> method is invoked a second time.</strong>
+     *
+     * @param iterator
+     *            The input <code>Iterator</code>
+     * @return The wrapping <code>Iterable</code>
+     */
+    @SuppressWarnings("unchecked")
+    public static Iterable<AccessControlPolicyIterator> in(AccessControlPolicyIterator iterator) {
+        return in((Iterator<AccessControlPolicyIterator>) iterator);
+    }
+
+    /**
+     * Transform an {@link EventIterator} into an {@link Iterable}
+     * <strong>for single use</strong> in a Java 5 for-each loop.
+     * <p>
+     * <strong>While general purpose <code>Iterables</code> tend to be reusable,
+     * this wrapper <code>Iterable</code> consumes the argument
+     * <code>Iterator</code>, leaving it in a non-reusable state. The returned
+     * <code>Iterable</code> will throw an <code>IllegalStateException</code> if
+     * its <code>iterator()</code> method is invoked a second time.</strong>
+     *
+     * @param iterator
+     *            The input <code>Iterator</code>
+     * @return The wrapping <code>Iterable</code>
+     */
+    @SuppressWarnings("unchecked")
+    public static Iterable<Event> in(EventIterator iterator) {
+        return in((Iterator<Event>) iterator);
+    }
+
+    /**
+     * Transform an {@link EventListenerIterator} into an {@link Iterable}
+     * <strong>for single use</strong> in a Java 5 for-each loop.
+     * <p>
+     * <strong>While general purpose <code>Iterables</code> tend to be reusable,
+     * this wrapper <code>Iterable</code> consumes the argument
+     * <code>Iterator</code>, leaving it in a non-reusable state. The returned
+     * <code>Iterable</code> will throw an <code>IllegalStateException</code> if
+     * its <code>iterator()</code> method is invoked a second time.</strong>
+     *
+     * @param iterator
+     *            The input <code>Iterator</code>
+     * @return The wrapping <code>Iterable</code>
+     */
+    @SuppressWarnings("unchecked")
+    public static Iterable<EventListener> in(EventListenerIterator iterator) {
+        return in((Iterator<EventListener>) iterator);
+    }
+
+    /**
+     * Transform an {@link NodeIterator} into an {@link Iterable}
+     * <strong>for single use</strong> in a Java 5 for-each loop.
+     * <p>
+     * <strong>While general purpose <code>Iterables</code> tend to be reusable,
+     * this wrapper <code>Iterable</code> consumes the argument
+     * <code>Iterator</code>, leaving it in a non-reusable state. The returned
+     * <code>Iterable</code> will throw an <code>IllegalStateException</code> if
+     * its <code>iterator()</code> method is invoked a second time.</strong>
+     *
+     * @param iterator
+     *            The input <code>Iterator</code>
+     * @return The wrapping <code>Iterable</code>
+     */
+    @SuppressWarnings("unchecked")
+    public static Iterable<Node> in(NodeIterator iterator) {
+        return in((Iterator<Node>) iterator);
+    }
+
+    /**
+     * Transform an {@link NodeTypeIterator} into an {@link Iterable}
+     * <strong>for single use</strong> in a Java 5 for-each loop.
+     * <p>
+     * <strong>While general purpose <code>Iterables</code> tend to be reusable,
+     * this wrapper <code>Iterable</code> consumes the argument
+     * <code>Iterator</code>, leaving it in a non-reusable state. The returned
+     * <code>Iterable</code> will throw an <code>IllegalStateException</code> if
+     * its <code>iterator()</code> method is invoked a second time.</strong>
+     *
+     * @param iterator
+     *            The input <code>Iterator</code>
+     * @return The wrapping <code>Iterable</code>
+     */
+    @SuppressWarnings("unchecked")
+    public static Iterable<NodeType> in(NodeTypeIterator iterator) {
+        return in((Iterator<NodeType>) iterator);
+    }
+
+    /**
+     * Transform an {@link PropertyIterator} into an {@link Iterable}
+     * <strong>for single use</strong> in a Java 5 for-each loop.
+     * <p>
+     * <strong>While general purpose <code>Iterables</code> tend to be reusable,
+     * this wrapper <code>Iterable</code> consumes the argument
+     * <code>Iterator</code>, leaving it in a non-reusable state. The returned
+     * <code>Iterable</code> will throw an <code>IllegalStateException</code> if
+     * its <code>iterator()</code> method is invoked a second time.</strong>
+     *
+     * @param iterator
+     *            The input <code>Iterator</code>
+     * @return The wrapping <code>Iterable</code>
+     */
+    @SuppressWarnings("unchecked")
+    public static Iterable<Property> in(PropertyIterator iterator) {
+        return in((Iterator<Property>) iterator);
+    }
+
+    /**
+     * Transform an {@link RowIterator} into an {@link Iterable}
+     * <strong>for single use</strong> in a Java 5 for-each loop.
+     * <p>
+     * <strong>While general purpose <code>Iterables</code> tend to be reusable,
+     * this wrapper <code>Iterable</code> consumes the argument
+     * <code>Iterator</code>, leaving it in a non-reusable state. The returned
+     * <code>Iterable</code> will throw an <code>IllegalStateException</code> if
+     * its <code>iterator()</code> method is invoked a second time.</strong>
+     *
+     * @param iterator
+     *            The input <code>Iterator</code>
+     * @return The wrapping <code>Iterable</code>
+     */
+    @SuppressWarnings("unchecked")
+    public static Iterable<Row> in(RowIterator iterator) {
+        return in((Iterator<Row>) iterator);
+    }
+
+    /**
+     * Transform an {@link VersionIterator} into an {@link Iterable}
+     * <strong>for single use</strong> in a Java 5 for-each loop.
+     * <p>
+     * <strong>While general purpose <code>Iterables</code> tend to be reusable,
+     * this wrapper <code>Iterable</code> consumes the argument
+     * <code>Iterator</code>, leaving it in a non-reusable state. The returned
+     * <code>Iterable</code> will throw an <code>IllegalStateException</code> if
+     * its <code>iterator()</code> method is invoked a second time.</strong>
+     *
+     * @param iterator
+     *            The input <code>Iterator</code>
+     * @return The wrapping <code>Iterable</code>
+     */
+    @SuppressWarnings("unchecked")
+    public static Iterable<Version> in(VersionIterator iterator) {
+        return in((Iterator<Version>) iterator);
     }
 
     /**
@@ -488,11 +927,7 @@ public class JcrUtils {
      */
     public static Node getOrAddNode(Node parent, String name)
             throws RepositoryException {
-        if (parent.hasNode(name)) {
-            return parent.getNode(name);
-        } else {
-            return parent.addNode(name);
-        }
+        return getOrAddNode(parent, name, null);
     }
 
     /**
@@ -506,7 +941,8 @@ public class JcrUtils {
      * @see Node#isNodeType(String)
      * @param parent parent node
      * @param name name of the child node
-     * @param type type of the child node, ignored if the child already exists
+     * @param type type of the child node or {@code null},
+     *             ignored if the child already exists
      * @return the child node
      * @throws RepositoryException if the child node can not be accessed
      *                             or created
@@ -515,8 +951,10 @@ public class JcrUtils {
             throws RepositoryException {
         if (parent.hasNode(name)) {
             return parent.getNode(name);
-        } else {
+        } else if (type != null) {
             return parent.addNode(name, type);
+        } else {
+            return parent.addNode(name);
         }
     }
 
@@ -667,7 +1105,7 @@ public class JcrUtils {
      * @return stream for reading the file contents
      * @throws RepositoryException if the file can not be accessed
      */
-    public InputStream readFile(Node node) throws RepositoryException {
+    public static InputStream readFile(Node node) throws RepositoryException {
         if (node.hasProperty(Property.JCR_DATA)) {
             Property data = node.getProperty(Property.JCR_DATA);
             final Binary binary = data.getBinary();
@@ -697,7 +1135,7 @@ public class JcrUtils {
      * @throws RepositoryException if the file can not be accessed
      * @throws IOException if the file can not be read or written
      */
-    public void readFile(Node node, OutputStream output)
+    public static void readFile(Node node, OutputStream output)
             throws RepositoryException, IOException {
         InputStream input = readFile(node);
         try {
@@ -722,7 +1160,7 @@ public class JcrUtils {
      * @return last modified date, or <code>null</code> if not available
      * @throws RepositoryException if the last modified date can not be accessed
      */
-    public Calendar getLastModified(Node node) throws RepositoryException {
+    public static Calendar getLastModified(Node node) throws RepositoryException {
         if (node.hasProperty(Property.JCR_LAST_MODIFIED)) {
             return node.getProperty(Property.JCR_LAST_MODIFIED).getDate();
         } else if (node.hasNode(Node.JCR_CONTENT)) {
@@ -742,7 +1180,7 @@ public class JcrUtils {
      * @param date modified date
      * @throws RepositoryException if the last modified date can not be set
      */
-    public void setLastModified(Node node, Calendar date) throws RepositoryException {
+    public static void setLastModified(Node node, Calendar date) throws RepositoryException {
         if (node.hasNode(Node.JCR_CONTENT)) {
             setLastModified(node.getNode(Node.JCR_CONTENT), date);
         } else {
@@ -1017,10 +1455,13 @@ public class JcrUtils {
     }
 
     /**
-     * Creates or gets the {@link javax.jcr.Node Node} at the given Path. In
-     * case it has to create the Node all non-existent intermediate
+     * Creates or gets the {@link javax.jcr.Node node} at the given path. In
+     * case it has to create the node, nodes for all non-existent intermediate
      * path-elements will be created with the given intermediate node type and
      * the returned node will be created with the given nodeType.
+     * <b>Note</b>: When the given path contains parent elements this method might
+     * create multiple nodes at leaf position (e.g "a/../b" will create the
+     * child nodes "a" and "b" on the current node).
      *
      * <p>
      * If the node name points to an existing node, the node name will be
@@ -1054,12 +1495,13 @@ public class JcrUtils {
     }
 
     /**
-     * Creates or gets the {@link javax.jcr.Node Node} at the given path
-     * relative to the baseNode. In case it has to create the Node all
-     * non-existent intermediate path-elements will be created with the given
+     * Creates or gets the {@link javax.jcr.Node node} at the given path
+     * relative to the baseNode. In case it has to create the node, nodes for
+     * all non-existent intermediate path-elements will be created with the given
      * intermediate node type and the returned node will be created with the
-     * given nodeType.
-     *
+     * given nodeType. <b>Note</b>: When the given path contains parent elements
+     * this method might create multiple nodes at leaf position (e.g "a/../b"
+     * will create the child nodes "a" and "b" on the current node).
      * <p>
      * If the parameter <code>createUniqueLeaf</code> is set, it will not get
      * an existing node but rather try to create a unique node by appending a
@@ -1162,4 +1604,302 @@ public class JcrUtils {
 
         return node.getNode(path);
     }
+
+    /**
+     * Get the node at <code>relPath</code> from <code>baseNode</code> or <code>null</code> if no such node exists.
+     *
+     * @param baseNode existing node that should be the base for the relative path
+     * @param relPath relative path to the node to get
+     * @return  the node at <code>relPath</code> from <code>baseNode</code> or <code>null</code> if no such node exists.
+     * @throws RepositoryException  in case of exception accessing the Repository
+     */
+    public static Node getNodeIfExists(Node baseNode, String relPath) throws RepositoryException {
+        try {
+            return baseNode.getNode(relPath);
+        } catch (PathNotFoundException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Gets the node at <code>absPath</code> or <code>null</code> if no such node exists.
+     *
+     * @param absPath  the absolute path to the node to return
+     * @param session  to use
+     * @return  the node at <code>absPath</code> or <code>null</code> if no such node exists.
+     * @throws RepositoryException  in case of exception accessing the Repository
+     */
+    public static Node getNodeIfExists(String absPath, Session session) throws RepositoryException {
+        try {
+            return session.getNode(absPath);
+        } catch (PathNotFoundException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the string property value at <code>relPath</code> from <code>baseNode</code> or <code>defaultValue</code>
+     * if no such property exists.
+     *
+     * @param baseNode  existing node that should be the base for the relative path
+     * @param relPath  relative path to the property to get
+     * @param defaultValue  default value to return when the property does not exist
+     * @return  the string property value at <code>relPath</code> from <code>baseNode</code> or <code>defaultValue</code>
+     * if no such property exists
+     * @throws RepositoryException  in case of exception accessing the Repository
+     */
+    public static String getStringProperty(Node baseNode, String relPath, String defaultValue) throws RepositoryException {
+        try {
+            return baseNode.getProperty(relPath).getString();
+        } catch (PathNotFoundException e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the long property value at <code>relPath</code> from <code>baseNode</code> or <code>defaultValue</code>
+     * if no such property exists.
+     *
+     * @param baseNode  existing node that should be the base for the relative path
+     * @param relPath  relative path to the property to get
+     * @param defaultValue  default value to return when the property does not exist
+     * @return  the long property value at <code>relPath</code> from <code>baseNode</code> or <code>defaultValue</code>
+     * if no such property exists
+     * @throws RepositoryException  in case of exception accessing the Repository
+     */
+    public static long getLongProperty(Node baseNode, String relPath, long defaultValue) throws RepositoryException {
+        try {
+            return baseNode.getProperty(relPath).getLong();
+        } catch (PathNotFoundException e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the double property value at <code>relPath</code> from <code>baseNode</code> or <code>defaultValue</code>
+     * if no such property exists.
+     *
+     * @param baseNode  existing node that should be the base for the relative path
+     * @param relPath  relative path to the property to get
+     * @param defaultValue  default value to return when the property does not exist
+     * @return  the double property value at <code>relPath</code> from <code>baseNode</code> or <code>defaultValue</code>
+     * if no such property exists
+     * @throws RepositoryException  in case of exception accessing the Repository
+     */
+    public static double getDoubleProperty(Node baseNode, String relPath, double defaultValue) throws RepositoryException {
+        try {
+            return baseNode.getProperty(relPath).getDouble();
+        } catch (PathNotFoundException e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the boolean property value at <code>relPath</code> from <code>baseNode</code> or <code>defaultValue</code>
+     * if no such property exists.
+     *
+     * @param baseNode  existing node that should be the base for the relative path
+     * @param relPath  relative path to the property to get
+     * @param defaultValue  default value to return when the property does not exist
+     * @return  the boolean property value at <code>relPath</code> from <code>baseNode</code> or <code>defaultValue</code>
+     * if no such property exists
+     * @throws RepositoryException  in case of exception accessing the Repository
+     */
+    public static boolean getBooleanProperty(Node baseNode, String relPath, boolean defaultValue) throws RepositoryException {
+        try {
+            return baseNode.getProperty(relPath).getBoolean();
+        } catch (PathNotFoundException e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the date property value at <code>relPath</code> from <code>baseNode</code> or <code>defaultValue</code>
+     * if no such property exists.
+     *
+     * @param baseNode  existing node that should be the base for the relative path
+     * @param relPath  relative path to the property to get
+     * @param defaultValue  default value to return when the property does not exist
+     * @return  the date property value at <code>relPath</code> from <code>baseNode</code> or <code>defaultValue</code>
+     * if no such property exists
+     * @throws RepositoryException  in case of exception accessing the Repository
+     */
+    public static Calendar getDateProperty(Node baseNode, String relPath, Calendar defaultValue) throws RepositoryException {
+        try {
+            return baseNode.getProperty(relPath).getDate();
+        } catch (PathNotFoundException e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the decimal property value at <code>relPath</code> from <code>baseNode</code> or <code>defaultValue</code>
+     * if no such property exists.
+     *
+     * @param baseNode  existing node that should be the base for the relative path
+     * @param relPath  relative path to the property to get
+     * @param defaultValue  default value to return when the property does not exist
+     * @return  the decimal property value at <code>relPath</code> from <code>baseNode</code> or <code>defaultValue</code>
+     * if no such property exists
+     * @throws RepositoryException  in case of exception accessing the Repository
+     */
+    public static BigDecimal getDecimalProperty(Node baseNode, String relPath, BigDecimal defaultValue) throws RepositoryException {
+        try {
+            return baseNode.getProperty(relPath).getDecimal();
+        } catch (PathNotFoundException e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the binary property value at <code>relPath</code> from <code>baseNode</code> or <code>defaultValue</code>
+     * if no such property exists.
+     *
+     * @param baseNode  existing node that should be the base for the relative path
+     * @param relPath  relative path to the property to get
+     * @param defaultValue  default value to return when the property does not exist
+     * @return  the binary property value at <code>relPath</code> from <code>baseNode</code> or <code>defaultValue</code>
+     * if no such property exists
+     * @throws RepositoryException  in case of exception accessing the Repository
+     */
+    public static Binary getBinaryProperty(Node baseNode, String relPath, Binary defaultValue) throws RepositoryException {
+        try {
+            return baseNode.getProperty(relPath).getBinary();
+        } catch (PathNotFoundException e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the string property value at <code>absPath</code> or <code>defaultValue</code>
+     * if no such property exists.
+     *
+     * @param session to use
+     * @param absPath  absolute path to the property to get
+     * @param defaultValue  default value to return when the property does not exist
+     * @return  the string property value at <code>absPath</code> or <code>defaultValue</code>
+     * if no such property exists
+     * @throws RepositoryException  in case of exception accessing the Repository
+     */
+    public static String getStringProperty(Session session, String absPath, String defaultValue) throws RepositoryException {
+        try {
+            return session.getProperty(absPath).getString();
+        } catch (PathNotFoundException e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the long property value at <code>absPath</code> or <code>defaultValue</code>
+     * if no such property exists.
+     *
+     * @param session  to use
+     * @param absPath  absolute path to the property to get
+     * @param defaultValue  default value to return when the property does not exist
+     * @return  the long property value at <code>absPath</code> or <code>defaultValue</code>
+     * if no such property exists
+     * @throws RepositoryException  in case of exception accessing the Repository
+     */
+    public static long getLongProperty(Session session, String absPath, long defaultValue) throws RepositoryException {
+        try {
+            return session.getProperty(absPath).getLong();
+        } catch (PathNotFoundException e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the double property value at <code>absPath</code> or <code>defaultValue</code>
+     * if no such property exists.
+     *
+     * @param session to use
+     * @param absPath  absolute path to the property to get
+     * @param defaultValue  default value to return when the property does not exist
+     * @return  the double property value at <code>absPath</code> or <code>defaultValue</code>
+     * if no such property exists
+     * @throws RepositoryException  in case of exception accessing the Repository
+     */
+    public static double getDoubleProperty(Session session, String absPath, double defaultValue) throws RepositoryException {
+        try {
+            return session.getProperty(absPath).getDouble();
+        } catch (PathNotFoundException e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the boolean property value at <code>absPath</code> or <code>defaultValue</code>
+     * if no such property exists.
+     *
+     * @param session to use
+     * @param absPath  absolute path to the property to get
+     * @param defaultValue  default value to return when the property does not exist
+     * @return  the boolean property value at <code>absPath</code> or <code>defaultValue</code>
+     * if no such property exists
+     * @throws RepositoryException  in case of exception accessing the Repository
+     */
+    public static boolean getBooleanProperty(Session session, String absPath, boolean defaultValue) throws RepositoryException {
+        try {
+            return session.getProperty(absPath).getBoolean();
+        } catch (PathNotFoundException e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the date property value at <code>absPath</code> or <code>defaultValue</code>
+     * if no such property exists.
+     *
+     * @param session to use
+     * @param absPath  absolute path to the property to get
+     * @param defaultValue  default value to return when the property does not exist
+     * @return  the date property value at <code>absPath</code> or <code>defaultValue</code>
+     * if no such property exists
+     * @throws RepositoryException  in case of exception accessing the Repository
+     */
+    public static Calendar getDateProperty(Session session, String absPath, Calendar defaultValue) throws RepositoryException {
+        try {
+            return session.getProperty(absPath).getDate();
+        } catch (PathNotFoundException e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the decimal property value at <code>absPath</code> or <code>defaultValue</code>
+     * if no such property exists.
+     *
+     * @param session to use
+     * @param absPath  absolute path to the property to get
+     * @param defaultValue  default value to return when the property does not exist
+     * @return  the decimal property value at <code>absPath</code> or <code>defaultValue</code>
+     * if no such property exists
+     * @throws RepositoryException  in case of exception accessing the Repository
+     */
+    public static BigDecimal getDecimalProperty(Session session, String absPath, BigDecimal defaultValue) throws RepositoryException {
+        try {
+            return session.getProperty(absPath).getDecimal();
+        } catch (PathNotFoundException e) {
+            return defaultValue;
+        }
+    }
+
+    /**
+     * Returns the binary property value at <code>absPath</code> or <code>defaultValue</code>
+     * if no such property exists.
+     *
+     * @param session to use
+     * @param absPath  absolute path to the property to get
+     * @param defaultValue  default value to return when the property does not exist
+     * @return  the binary property value at <code>absPath</code> or <code>defaultValue</code>
+     * if no such property exists
+     * @throws RepositoryException  in case of exception accessing the Repository
+     */
+    public static Binary getBinaryProperty(Session session, String absPath, Binary defaultValue) throws RepositoryException {
+        try {
+            return session.getProperty(absPath).getBinary();
+        } catch (PathNotFoundException e) {
+            return defaultValue;
+        }
+    }
 }
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/NamespaceHelper.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/NamespaceHelper.java
index fa0e889..a558118 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/NamespaceHelper.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/NamespaceHelper.java
@@ -1,240 +1,240 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.commons;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.NamespaceException;
-import javax.jcr.NamespaceRegistry;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.jackrabbit.util.XMLChar;
-
-/**
- * Helper class for working with JCR namespaces.
- *
- * @since Jackrabbit JCR Commons 1.5
- */
-public class NamespaceHelper {
-
-    /**
-     * The <code>jcr</code> namespace URI.
-     */
-    public static final String JCR = "http://www.jcp.org/jcr/1.0";
-
-    /**
-     * The <code>nt</code> namespace URI.
-     */
-    public static final String NT = "http://www.jcp.org/jcr/nt/1.0";
-
-    /**
-     * The <code>mix</code> namespace URI.
-     */
-    public static final String MIX = "http://www.jcp.org/jcr/mix/1.0";
-
-    /**
-     * Current session.
-     */
-    private final Session session;
-
-    /**
-     * Creates a namespace helper for the given session.
-     *
-     * @param session current session
-     */
-    public NamespaceHelper(Session session) {
-        this.session = session;
-    }
-
-    /**
-     * Returns a map containing all prefix to namespace URI mappings of
-     * the current session. The returned map is newly allocated and can
-     * can be freely modified by the caller.
-     *
-     * @see Session#getNamespacePrefixes()
-     * @return namespace mappings
-     * @throws RepositoryException if the namespaces could not be retrieved
-     */
-    public Map<String, String> getNamespaces() throws RepositoryException {
-        Map<String, String> namespaces = new HashMap<String, String>();
-        String[] prefixes = session.getNamespacePrefixes();
-        for (String prefixe : prefixes) {
-            namespaces.put(prefixe, session.getNamespaceURI(prefixe));
-        }
-        return namespaces;
-    }
-
-    /**
-     * Returns the prefix mapped to the given namespace URI in the current
-     * session, or <code>null</code> if the namespace does not exist.
-     *
-     * @see Session#getNamespacePrefix(String)
-     * @param uri namespace URI
-     * @return namespace prefix, or <code>null</code>
-     * @throws RepositoryException if the namespace could not be retrieved
-     */
-    public String getPrefix(String uri) throws RepositoryException {
-        try {
-            return session.getNamespacePrefix(uri);
-        } catch (NamespaceException e) {
-            return null;
-        }
-    }
-
-    /**
-     * Returns the namespace URI mapped to the given prefix in the current
-     * session, or <code>null</code> if the namespace does not exist.
-     *
-     * @see Session#getNamespaceURI(String)
-     * @param prefix namespace prefix
-     * @return namespace prefix, or <code>null</code>
-     * @throws RepositoryException if the namespace could not be retrieved
-     */
-    public String getURI(String prefix) throws RepositoryException {
-        try {
-            return session.getNamespaceURI(prefix);
-        } catch (NamespaceException e) {
-            return null;
-        }
-    }
-
-    /**
-     * Returns the prefixed JCR name for the given namespace URI and local
-     * name in the current session.
-     *
-     * @param uri namespace URI
-     * @param name local name
-     * @return prefixed JCR name
-     * @throws NamespaceException if the namespace does not exist
-     * @throws RepositoryException if the namespace could not be retrieved
-     */
-    public String getJcrName(String uri, String name)
-            throws NamespaceException, RepositoryException {
-        if (uri != null && uri.length() > 0) {
-            return session.getNamespacePrefix(uri) + ":" + name;
-        } else {
-            return name;
-        }
-    }
-
-    /**
-     * Replaces the standard <code>jcr</code>, <code>nt</code>, or
-     * <code>mix</code> prefix in the given name with the prefix
-     * mapped to that namespace in the current session.
-     * <p>
-     * The purpose of this method is to make it easier to write
-     * namespace-aware code that uses names in the standard JCR namespaces.
-     * For example:
-     * <pre>
-     *     node.getProperty(helper.getName("jcr:data"));
-     * </pre>
-     *
-     * @param name prefixed name using the standard JCR prefixes
-     * @return prefixed name using the current session namespace mappings
-     * @throws IllegalArgumentException if the prefix is unknown
-     * @throws RepositoryException if the namespace could not be retrieved
-     */
-    public String getJcrName(String name)
-            throws IllegalArgumentException, RepositoryException {
-        String standardPrefix;
-        String currentPrefix;
-
-        if (name.startsWith("jcr:")) {
-            standardPrefix = "jcr";
-            currentPrefix = session.getNamespacePrefix(JCR);
-        } else if (name.startsWith("nt:")) {
-            standardPrefix = "nt";
-            currentPrefix = session.getNamespacePrefix(NT);
-        } else if (name.startsWith("mix:")) {
-            standardPrefix = "mix";
-            currentPrefix = session.getNamespacePrefix(MIX);
-        } else {
-            throw new IllegalArgumentException("Unknown prefix: " + name);
-        }
-
-        if (currentPrefix.equals(standardPrefix)) {
-            return name;
-        } else {
-            return currentPrefix + name.substring(standardPrefix.length());
-        }
-    }
-
-    /**
-     * Safely registers the given namespace. If the namespace already exists,
-     * then the prefix mapped to the namespace in the current session is
-     * returned. Otherwise the namespace is registered to the namespace
-     * registry. If the given prefix is already registered for some other
-     * namespace or otherwise invalid, then another prefix is automatically
-     * generated. After the namespace has been registered, the prefix mapped
-     * to it in the current session is returned.
-     *
-     * @see NamespaceRegistry#registerNamespace(String, String)
-     * @param prefix namespace prefix
-     * @param uri namespace URI
-     * @return namespace prefix in the current session
-     * @throws RepositoryException if the namespace could not be registered
-     */
-    public String registerNamespace(String prefix, String uri)
-            throws RepositoryException {
-        NamespaceRegistry registry =
-            session.getWorkspace().getNamespaceRegistry();
-        try {
-            // Check if the namespace is registered
-            registry.getPrefix(uri);
-        } catch (NamespaceException e1) {
-             // Replace troublesome prefix hints
-            if (prefix == null || prefix.length() == 0
-                    || prefix.toLowerCase().startsWith("xml")
-                    || !XMLChar.isValidNCName(prefix)) {
-                prefix = "ns"; // ns, ns2, ns3, ns4, ...
-            }
-
-            // Loop until an unused prefix is found
-            try {
-                String base = prefix;
-                for (int i = 2; true; i++) {
-                    registry.getURI(prefix);
-                    prefix = base + i;
-                }
-            } catch (NamespaceException e2) {
-                // Exit the loop
-            } 
-
-            // Register the namespace
-            registry.registerNamespace(prefix, uri);
-        }
-
-        return session.getNamespacePrefix(uri);
-    }
-
-    /**
-     * Safely registers all namespaces in the given map from
-     * prefixes to namespace URIs.
-     *
-     * @param namespaces namespace mappings
-     * @throws RepositoryException if the namespaces could not be registered
-     */
-    public void registerNamespaces(Map<String,String> namespaces) throws RepositoryException {
-        for (Map.Entry<String, String> stringStringEntry : namespaces.entrySet()) {
-            Map.Entry<String, String> entry = stringStringEntry;
-            registerNamespace(entry.getKey(), entry.getValue());
-        }
-    }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.commons;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.NamespaceException;
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.util.XMLChar;
+
+/**
+ * Helper class for working with JCR namespaces.
+ *
+ * @since Jackrabbit JCR Commons 1.5
+ */
+public class NamespaceHelper {
+
+    /**
+     * The <code>jcr</code> namespace URI.
+     */
+    public static final String JCR = "http://www.jcp.org/jcr/1.0";
+
+    /**
+     * The <code>nt</code> namespace URI.
+     */
+    public static final String NT = "http://www.jcp.org/jcr/nt/1.0";
+
+    /**
+     * The <code>mix</code> namespace URI.
+     */
+    public static final String MIX = "http://www.jcp.org/jcr/mix/1.0";
+
+    /**
+     * Current session.
+     */
+    private final Session session;
+
+    /**
+     * Creates a namespace helper for the given session.
+     *
+     * @param session current session
+     */
+    public NamespaceHelper(Session session) {
+        this.session = session;
+    }
+
+    /**
+     * Returns a map containing all prefix to namespace URI mappings of
+     * the current session. The returned map is newly allocated and can
+     * can be freely modified by the caller.
+     *
+     * @see Session#getNamespacePrefixes()
+     * @return namespace mappings
+     * @throws RepositoryException if the namespaces could not be retrieved
+     */
+    public Map<String, String> getNamespaces() throws RepositoryException {
+        Map<String, String> namespaces = new HashMap<String, String>();
+        String[] prefixes = session.getNamespacePrefixes();
+        for (String prefixe : prefixes) {
+            namespaces.put(prefixe, session.getNamespaceURI(prefixe));
+        }
+        return namespaces;
+    }
+
+    /**
+     * Returns the prefix mapped to the given namespace URI in the current
+     * session, or <code>null</code> if the namespace does not exist.
+     *
+     * @see Session#getNamespacePrefix(String)
+     * @param uri namespace URI
+     * @return namespace prefix, or <code>null</code>
+     * @throws RepositoryException if the namespace could not be retrieved
+     */
+    public String getPrefix(String uri) throws RepositoryException {
+        try {
+            return session.getNamespacePrefix(uri);
+        } catch (NamespaceException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the namespace URI mapped to the given prefix in the current
+     * session, or <code>null</code> if the namespace does not exist.
+     *
+     * @see Session#getNamespaceURI(String)
+     * @param prefix namespace prefix
+     * @return namespace prefix, or <code>null</code>
+     * @throws RepositoryException if the namespace could not be retrieved
+     */
+    public String getURI(String prefix) throws RepositoryException {
+        try {
+            return session.getNamespaceURI(prefix);
+        } catch (NamespaceException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the prefixed JCR name for the given namespace URI and local
+     * name in the current session.
+     *
+     * @param uri namespace URI
+     * @param name local name
+     * @return prefixed JCR name
+     * @throws NamespaceException if the namespace does not exist
+     * @throws RepositoryException if the namespace could not be retrieved
+     */
+    public String getJcrName(String uri, String name)
+            throws NamespaceException, RepositoryException {
+        if (uri != null && uri.length() > 0) {
+            return session.getNamespacePrefix(uri) + ":" + name;
+        } else {
+            return name;
+        }
+    }
+
+    /**
+     * Replaces the standard <code>jcr</code>, <code>nt</code>, or
+     * <code>mix</code> prefix in the given name with the prefix
+     * mapped to that namespace in the current session.
+     * <p>
+     * The purpose of this method is to make it easier to write
+     * namespace-aware code that uses names in the standard JCR namespaces.
+     * For example:
+     * <pre>
+     *     node.getProperty(helper.getName("jcr:data"));
+     * </pre>
+     *
+     * @param name prefixed name using the standard JCR prefixes
+     * @return prefixed name using the current session namespace mappings
+     * @throws IllegalArgumentException if the prefix is unknown
+     * @throws RepositoryException if the namespace could not be retrieved
+     */
+    public String getJcrName(String name)
+            throws IllegalArgumentException, RepositoryException {
+        String standardPrefix;
+        String currentPrefix;
+
+        if (name.startsWith("jcr:")) {
+            standardPrefix = "jcr";
+            currentPrefix = session.getNamespacePrefix(JCR);
+        } else if (name.startsWith("nt:")) {
+            standardPrefix = "nt";
+            currentPrefix = session.getNamespacePrefix(NT);
+        } else if (name.startsWith("mix:")) {
+            standardPrefix = "mix";
+            currentPrefix = session.getNamespacePrefix(MIX);
+        } else {
+            throw new IllegalArgumentException("Unknown prefix: " + name);
+        }
+
+        if (currentPrefix.equals(standardPrefix)) {
+            return name;
+        } else {
+            return currentPrefix + name.substring(standardPrefix.length());
+        }
+    }
+
+    /**
+     * Safely registers the given namespace. If the namespace already exists,
+     * then the prefix mapped to the namespace in the current session is
+     * returned. Otherwise the namespace is registered to the namespace
+     * registry. If the given prefix is already registered for some other
+     * namespace or otherwise invalid, then another prefix is automatically
+     * generated. After the namespace has been registered, the prefix mapped
+     * to it in the current session is returned.
+     *
+     * @see NamespaceRegistry#registerNamespace(String, String)
+     * @param prefix namespace prefix
+     * @param uri namespace URI
+     * @return namespace prefix in the current session
+     * @throws RepositoryException if the namespace could not be registered
+     */
+    public String registerNamespace(String prefix, String uri)
+            throws RepositoryException {
+        NamespaceRegistry registry =
+            session.getWorkspace().getNamespaceRegistry();
+        try {
+            // Check if the namespace is registered
+            registry.getPrefix(uri);
+        } catch (NamespaceException e1) {
+             // Replace troublesome prefix hints
+            if (prefix == null || prefix.length() == 0
+                    || prefix.toLowerCase().startsWith("xml")
+                    || !XMLChar.isValidNCName(prefix)) {
+                prefix = "ns"; // ns, ns2, ns3, ns4, ...
+            }
+
+            // Loop until an unused prefix is found
+            try {
+                String base = prefix;
+                for (int i = 2; true; i++) {
+                    registry.getURI(prefix);
+                    prefix = base + i;
+                }
+            } catch (NamespaceException e2) {
+                // Exit the loop
+            } 
+
+            // Register the namespace
+            registry.registerNamespace(prefix, uri);
+        }
+
+        return session.getNamespacePrefix(uri);
+    }
+
+    /**
+     * Safely registers all namespaces in the given map from
+     * prefixes to namespace URIs.
+     *
+     * @param namespaces namespace mappings
+     * @throws RepositoryException if the namespaces could not be registered
+     */
+    public void registerNamespaces(Map<String,String> namespaces) throws RepositoryException {
+        for (Map.Entry<String, String> stringStringEntry : namespaces.entrySet()) {
+            Map.Entry<String, String> entry = stringStringEntry;
+            registerNamespace(entry.getKey(), entry.getValue());
+        }
+    }
+
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/cnd/CndImporter.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/cnd/CndImporter.java
index e094b5a..2d16083 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/cnd/CndImporter.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/cnd/CndImporter.java
@@ -19,7 +19,9 @@ package org.apache.jackrabbit.commons.cnd;
 import java.io.IOException;
 import java.io.Reader;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import javax.jcr.NamespaceRegistry;
 import javax.jcr.RepositoryException;
@@ -27,15 +29,19 @@ import javax.jcr.Session;
 import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.ValueFactory;
 import javax.jcr.Workspace;
+import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.nodetype.InvalidNodeTypeDefinitionException;
 import javax.jcr.nodetype.NodeDefinitionTemplate;
 import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.NodeTypeDefinition;
 import javax.jcr.nodetype.NodeTypeExistsException;
 import javax.jcr.nodetype.NodeTypeIterator;
 import javax.jcr.nodetype.NodeTypeManager;
 import javax.jcr.nodetype.NodeTypeTemplate;
 import javax.jcr.nodetype.PropertyDefinitionTemplate;
 
+import static org.apache.jackrabbit.JcrConstants.NT_BASE;
+
 /**
  * Utility class for importing compact node type definitions.
  * @see CompactNodeTypeDefReader
@@ -141,22 +147,20 @@ public final class CndImporter {
             CompactNodeTypeDefReader<NodeTypeTemplate, NamespaceRegistry> cndReader =
                 new CompactNodeTypeDefReader<NodeTypeTemplate, NamespaceRegistry>(cnd, systemId, factory);
 
-            List<NodeTypeTemplate> ntts = cndReader.getNodeTypeDefinitions();
+            Map<String, NodeTypeTemplate> templates = new HashMap<String, NodeTypeTemplate>();
+            for (NodeTypeTemplate template : cndReader.getNodeTypeDefinitions()) {
+                templates.put(template.getName(), template);
+            }
 
-            NodeTypeIterator registered;
-            if (reregisterExisting) {
-                registered = nodeTypeManager.registerNodeTypes(ntts.toArray(new NodeTypeTemplate[ntts.size()]), true);
-            } else {
-                List<NodeTypeTemplate> toRegister = new ArrayList<NodeTypeTemplate>(ntts.size());
-                for (NodeTypeTemplate ntt : ntts) {
-                    if (!nodeTypeManager.hasNodeType(ntt.getName())) {
-                        toRegister.add(ntt);
-                    }
+            List<NodeTypeTemplate> toRegister = new ArrayList<NodeTypeTemplate>(templates.size());
+            for (NodeTypeTemplate ntt : templates.values()) {
+                if (reregisterExisting || !nodeTypeManager.hasNodeType(ntt.getName())) {
+                    ensureNtBase(ntt, templates, nodeTypeManager);
+                    toRegister.add(ntt);
                 }
-
-                registered = nodeTypeManager.registerNodeTypes(toRegister.toArray(new NodeTypeTemplate[toRegister.size()]), true);
             }
-
+            NodeTypeIterator registered = nodeTypeManager.registerNodeTypes(
+                    toRegister.toArray(new NodeTypeTemplate[toRegister.size()]), true);
             return toArray(registered);
         }
         finally {
@@ -164,6 +168,34 @@ public final class CndImporter {
         }
     }
 
+    private static void ensureNtBase(NodeTypeTemplate ntt, Map<String, NodeTypeTemplate> templates,
+            NodeTypeManager nodeTypeManager) throws RepositoryException {
+        if (!ntt.isMixin() && !NT_BASE.equals(ntt.getName())) {
+            String[] supertypes = ntt.getDeclaredSupertypeNames();
+            if (supertypes.length == 0) {
+                ntt.setDeclaredSuperTypeNames(new String[] {NT_BASE});
+            } else {
+                // Check whether we need to add the implicit "nt:base" supertype
+                boolean needsNtBase = true;
+                for (String name : supertypes) {
+                    NodeTypeDefinition std = templates.get(name);
+                    if (std == null) {
+                        std = nodeTypeManager.getNodeType(name);
+                    }
+                    if (std != null && !std.isMixin()) {
+                        needsNtBase = false;
+                    }
+                }
+                if (needsNtBase) {
+                    String[] withNtBase = new String[supertypes.length + 1];
+                    withNtBase[0] = NT_BASE;
+                    System.arraycopy(supertypes, 0, withNtBase, 1, supertypes.length);
+                    ntt.setDeclaredSuperTypeNames(withNtBase);
+                }
+            }
+        }
+    }
+
     // -----------------------------------------------------< private >---
 
     private static NodeType[] toArray(NodeTypeIterator nodeTypes) {
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/cnd/CompactNodeTypeDefReader.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/cnd/CompactNodeTypeDefReader.java
index b9febf7..c9523b6 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/cnd/CompactNodeTypeDefReader.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/cnd/CompactNodeTypeDefReader.java
@@ -32,14 +32,14 @@ import java.util.List;
  * CompactNodeTypeDefReader. Parses node type definitions written in the compact
  * node type definition format and provides a list of type definition
  * objects that can then be used to register node types.
- * <p/>
+ * <p>
  * The CompactNodeTypeDefReader is parameterizable in the type of the node type
  * definition <code>T</code> and the type of the namespace mapping <code>N</code>
  * which the parser should build. For types <code>T</code> and <code>N</code> the
  * parser's constructor takes a {@link DefinitionBuilderFactory} for
  * <code>T</code> and <code>N</code>.
- * <p/>
- * <p/>
+ * <p>
+ * <p>
  * The EBNF grammar of the compact node type definition:<br>
  * <pre>
  * Cnd ::= {NamespaceMapping | NodeTypeDef}
@@ -402,7 +402,7 @@ public class CompactNodeTypeDefReader<T, N> {
         try {
             pd.setName(currentToken);
         } catch (RepositoryException e) {
-            lexer.fail("Invalid property name '" + currentToken + "': " + e.getMessage());
+            lexer.fail("Invalid property name '" + currentToken, e);
         }
         nextToken();
         doPropertyType(pd);
@@ -454,7 +454,7 @@ public class CompactNodeTypeDefReader<T, N> {
                 lexer.fail("Unkown property type '" + currentToken + "' specified");
             }
         } catch (RepositoryException e) {
-            lexer.fail("Error setting property type of " + pd.getName() + " to " + currentToken);
+            lexer.fail("Error setting property type of " + pd.getName() + " to " + currentToken, e);
         }
         nextToken();
         if (!currentTokenEquals(Lexer.END_TYPE)) {
@@ -507,7 +507,7 @@ public class CompactNodeTypeDefReader<T, N> {
                 nextToken();
             }
         } catch (RepositoryException e) {
-            lexer.fail("Error setting property attribute of " + pd.getName() + " to " + currentToken);
+            lexer.fail("Error setting property attribute of " + pd.getName() + " to " + currentToken, e);
         }
     }
 
@@ -549,7 +549,7 @@ public class CompactNodeTypeDefReader<T, N> {
         try {
             pd.setAvailableQueryOperators(queryOps.toArray(new String[queryOps.size()]));
         } catch (RepositoryException e) {
-            lexer.fail("Error query operators for " + pd.getName() + " to " + currentToken);
+            lexer.fail("Error query operators for " + pd.getName() + " to " + currentToken, e);
         }
     }
 
@@ -571,7 +571,7 @@ public class CompactNodeTypeDefReader<T, N> {
             try {
                 pd.addDefaultValues(currentToken);
             } catch (RepositoryException e) {
-                lexer.fail("Error adding default value for " + pd.getName() + " to " + currentToken + ": " + e.getMessage());
+                lexer.fail("Error adding default value for " + pd.getName() + " to " + currentToken, e);
             }
             nextToken();
         } while (currentTokenEquals(Lexer.LIST_DELIMITER));
@@ -595,7 +595,7 @@ public class CompactNodeTypeDefReader<T, N> {
             try {
                 pd.addValueConstraint(currentToken);
             } catch (RepositoryException e) {
-                lexer.fail("Error adding value constraint for " + pd.getName() + " to " + currentToken + ": " + e.getMessage());
+                lexer.fail("Error adding value constraint for " + pd.getName() + " to " + currentToken, e);
             }
             nextToken();
         } while (currentTokenEquals(Lexer.LIST_DELIMITER));
@@ -615,7 +615,7 @@ public class CompactNodeTypeDefReader<T, N> {
         try {
             nd.setName(currentToken);
         } catch (RepositoryException e) {
-            lexer.fail("Invalid child node name '" + currentToken + "': " + e.getMessage());
+            lexer.fail("Invalid child node name '" + currentToken, e);
         }
         nextToken();
         doChildNodeRequiredTypes(nd);
@@ -641,7 +641,7 @@ public class CompactNodeTypeDefReader<T, N> {
             try {
                 nd.addRequiredPrimaryType(currentToken);
             } catch (RepositoryException e) {
-                lexer.fail("Error setting required primary type of " + nd.getName() + " to " + currentToken + ": " + e.getMessage());
+                lexer.fail("Error setting required primary type of " + nd.getName() + " to " + currentToken, e);
             }
             nextToken();
         } while (currentTokenEquals(Lexer.LIST_DELIMITER));
@@ -664,7 +664,7 @@ public class CompactNodeTypeDefReader<T, N> {
         try {
             nd.setDefaultPrimaryType(currentToken);
         } catch (RepositoryException e) {
-            lexer.fail("Error setting default primary type of " + nd.getName() + " to " + currentToken + ": " + e.getMessage());
+            lexer.fail("Error setting default primary type of " + nd.getName() + " to " + currentToken, e);
         }
         nextToken();
     }
@@ -708,7 +708,7 @@ public class CompactNodeTypeDefReader<T, N> {
                 nextToken();
             }
         } catch (RepositoryException e) {
-            lexer.fail("Error setting child node attribute of " + nd.getName() + " to " + currentToken);
+            lexer.fail("Error setting child node attribute of " + nd.getName() + " to " + currentToken, e);
         }
     }
 
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/flat/FilterIterator.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/flat/FilterIterator.java
index c9e1c07..8b18b42 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/flat/FilterIterator.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/flat/FilterIterator.java
@@ -31,7 +31,7 @@ public class FilterIterator<T> extends org.apache.jackrabbit.commons.iterator.Fi
     /**
      * Create a new filtered iterator based on the given <code>iterator</code>.
      *
-     * @param iterator  iterator to filter
+     * @param tIterator  iterator to filter
      * @param predicate only item matching this predicate are included
      */
     public FilterIterator(Iterator<T> tIterator, Predicate predicate) {
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/AbstractLazyIterator.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/AbstractLazyIterator.java
new file mode 100644
index 0000000..289ecf0
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/AbstractLazyIterator.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.commons.iterator;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * {@code AbstractLazyIterator} provides basic iteration methods for a lazy loading iterator that does not support
+ * remove. Implementing classes only need to implement the {@link #getNext()} method which must return the next item
+ * in the iteration or {@code null} if the iteration as reached its end.
+ */
+public abstract class AbstractLazyIterator<T> implements Iterator<T> {
+
+    private boolean fetchNext = true;
+
+    private T next;
+
+    protected AbstractLazyIterator() {
+    }
+
+    @Override
+    public boolean hasNext() {
+        if (fetchNext) {
+            next = getNext();
+            fetchNext = false;
+        }
+        return next != null;
+    }
+
+    @Override
+    public T next() {
+        if (fetchNext) {
+            next = getNext();
+        } else {
+            fetchNext = true;
+        }
+        if (next == null) {
+            throw new NoSuchElementException();
+        }
+        return next;
+    }
+
+    /**
+     * Returns the next element of this iteration or {@code null} if the iteration has finished.
+     * @return the next element.
+     */
+    abstract protected T getNext();
+
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException();
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/FilteredRangeIterator.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/FilteredRangeIterator.java
index efb1000..596d18a 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/FilteredRangeIterator.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/FilteredRangeIterator.java
@@ -96,7 +96,6 @@ public class FilteredRangeIterator implements RangeIterator {
      * 
      *
      * @param iterator underlying iterator
-     * @param predicate predicate used for filtering
      */
     public FilteredRangeIterator(Iterator<?> iterator) {
         this(iterator, Predicate.TRUE, 1000);
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/NodeIterable.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/NodeIterable.java
index 7954916..d3ef2f5 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/NodeIterable.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/NodeIterable.java
@@ -21,13 +21,17 @@ import java.util.Iterator;
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
 
+import org.apache.jackrabbit.commons.JcrUtils;
+
 /**
  * Adapter class that adapts a {@link NodeIterator} instance to an
  * {@link Iterable<Node>} instance that always returns the same underlying
  * iterator.
  *
  * @since Apache Jackrabbit 2.0
+ * @deprecated - Use {@link JcrUtils#in(NodeIterator)} instead
  */
+ at Deprecated
 public class NodeIterable implements Iterable<Node> {
 
     /**
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/NodeIteratorAdapter.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/NodeIteratorAdapter.java
index f29950a..30caabb 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/NodeIteratorAdapter.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/NodeIteratorAdapter.java
@@ -55,6 +55,10 @@ public class NodeIteratorAdapter extends RangeIteratorDecorator
         super(new RangeIteratorAdapter(iterator));
     }
 
+    public NodeIteratorAdapter(Iterator iterator, long size) {
+        super(new RangeIteratorAdapter(iterator, size));
+    }
+
     /**
      * Creates an iterator for the given collection.
      *
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/PropertyIterable.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/PropertyIterable.java
index 14fac9c..d0fa4f9 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/PropertyIterable.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/PropertyIterable.java
@@ -21,13 +21,17 @@ import java.util.Iterator;
 import javax.jcr.Property;
 import javax.jcr.PropertyIterator;
 
+import org.apache.jackrabbit.commons.JcrUtils;
+
 /**
  * Adapter class that adapts a {@link PropertyIterator} instance to an
  * {@link Iterable<Property>} instance that always returns the same underlying
  * iterator.
  *
  * @since Apache Jackrabbit 2.0
+ * @deprecated - Use {@link JcrUtils#in(PropertyIterator)} instead
  */
+ at Deprecated
 public class PropertyIterable implements Iterable<Property> {
 
     /**
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/PropertyIteratorAdapter.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/PropertyIteratorAdapter.java
index 0c00012..fa964ff 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/PropertyIteratorAdapter.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/PropertyIteratorAdapter.java
@@ -55,6 +55,10 @@ public class PropertyIteratorAdapter extends RangeIteratorDecorator
         super(new RangeIteratorAdapter(iterator));
     }
 
+    public PropertyIteratorAdapter(Iterator iterator, long size) {
+        super(new RangeIteratorAdapter(iterator, size));
+    }
+
     /**
      * Creates an iterator for the given collection.
      *
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/RowIterable.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/RowIterable.java
index 6c555ab..099bb61 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/RowIterable.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/RowIterable.java
@@ -21,13 +21,17 @@ import java.util.Iterator;
 import javax.jcr.query.Row;
 import javax.jcr.query.RowIterator;
 
+import org.apache.jackrabbit.commons.JcrUtils;
+
 /**
  * Adapter class that adapts a {@link RowIterator} instance to an
  * {@link Iterable<Row>} instance that always returns the same underlying
  * iterator.
  *
  * @since Apache Jackrabbit 2.0
+ * @deprecated - Use {@link JcrUtils#in(RowIterator)} instead
  */
+ at Deprecated
 public class RowIterable implements Iterable<Row> {
 
     /**
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/package-info.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/package-info.java
index 43ebb94..6f552ce 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/package-info.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/iterator/package-info.java
@@ -14,5 +14,5 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- at aQute.bnd.annotation.Version("2.3")
+ at aQute.bnd.annotation.Version("2.4.0")
 package org.apache.jackrabbit.commons.iterator;
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/jackrabbit/SimpleReferenceBinary.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/jackrabbit/SimpleReferenceBinary.java
new file mode 100644
index 0000000..e864444
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/jackrabbit/SimpleReferenceBinary.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.commons.jackrabbit;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.api.ReferenceBinary;
+import org.apache.jackrabbit.api.ReferenceBinaryException;
+
+public class SimpleReferenceBinary implements ReferenceBinary {
+
+    private final String reference;
+
+    public SimpleReferenceBinary(String reference) {
+        this.reference = reference;
+    }
+
+    //---------------------------------------------------< ReferenceBinary >--
+
+    @Override
+    public String getReference() {
+        return reference;
+    }
+
+    //------------------------------------------------------------< Binary >--
+
+    @Override
+    public InputStream getStream() throws RepositoryException {
+        throw new ReferenceBinaryException(
+                "Broken binary reference: " + reference);
+    }
+
+    @Override
+    public int read(byte[] b, long position)
+            throws IOException, RepositoryException {
+        throw new ReferenceBinaryException(
+                "Broken binary reference: " + reference);
+    }
+
+    @Override
+    public long getSize() throws RepositoryException {
+        throw new ReferenceBinaryException(
+                "Broken binary reference: " + reference);
+    }
+
+    @Override
+    public void dispose() {
+        // ignore
+    }
+
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/jackrabbit/authorization/AccessControlUtils.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/jackrabbit/authorization/AccessControlUtils.java
new file mode 100644
index 0000000..bb90a22
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/jackrabbit/authorization/AccessControlUtils.java
@@ -0,0 +1,390 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.commons.jackrabbit.authorization;
+
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.security.AccessControlEntry;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.AccessControlPolicyIterator;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+
+/**
+ * This class provides common access control related utilities.
+ */
+public class AccessControlUtils {
+
+    /**
+     * Retrieves the {@link Privilege}s from the specified privilege names.
+     *
+     * @param session The editing session.
+     * @param privilegeNames The privilege names.
+     * @return An array of privileges.
+     * @throws RepositoryException If an error occurs or if {@code privilegeNames}
+     * contains an unknown/invalid privilege name.
+     */
+    public static Privilege[] privilegesFromNames(Session session, String... privilegeNames) throws RepositoryException {
+        return privilegesFromNames(session.getAccessControlManager(), privilegeNames);
+    }
+
+    /**
+     * Retrieves the {@link Privilege}s from the specified privilege names.
+     *
+     * @param accessControlManager The access control manager.
+     * @param privilegeNames The privilege names.
+     * @return An array of privileges.
+     * @throws RepositoryException If an error occurs or if {@code privilegeNames}
+     * contains an unknown/invalid privilege name.
+     */
+    public static Privilege[] privilegesFromNames(AccessControlManager accessControlManager, String... privilegeNames) throws RepositoryException {
+        Set<Privilege> privileges = new HashSet<Privilege>(privilegeNames.length);
+        for (String privName : privilegeNames) {
+            privileges.add(accessControlManager.privilegeFromName(privName));
+        }
+        return privileges.toArray(new Privilege[privileges.size()]);
+    }
+
+    /**
+     * Retrieves the names of the specified privileges.
+     *
+     * @param privileges One or more privileges.
+     * @return The names of the specified privileges.
+     */
+    public static String[] namesFromPrivileges(Privilege... privileges) {
+        if (privileges == null || privileges.length == 0) {
+            return new String[0];
+        } else {
+            String[] names = new String[privileges.length];
+            for (int i = 0; i < privileges.length; i++) {
+                names[i] = privileges[i].getName();
+            }
+            return names;
+        }
+    }
+
+    /**
+     * Utility that combines {@link AccessControlManager#getApplicablePolicies(String)}
+     * and {@link AccessControlManager#getPolicies(String)} to retrieve
+     * a modifiable {@code JackrabbitAccessControlList} for the given path.<br>
+     *
+     * Note that the policy must be {@link AccessControlManager#setPolicy(String,
+     * javax.jcr.security.AccessControlPolicy) reapplied}
+     * and the changes must be saved in order to make the AC modifications take
+     * effect.
+     *
+     * @param session The editing session.
+     * @param absPath The absolute path of the target node.
+     * @return A modifiable access control list or null if there is none.
+     * @throws RepositoryException If an error occurs.
+     */
+    public static JackrabbitAccessControlList getAccessControlList(Session session, String absPath) throws RepositoryException {
+        AccessControlManager acMgr = session.getAccessControlManager();
+        return getAccessControlList(acMgr, absPath);
+    }
+
+    /**
+     * Utility that combines {@link AccessControlManager#getApplicablePolicies(String)}
+     * and {@link AccessControlManager#getPolicies(String)} to retrieve
+     * a modifiable {@code JackrabbitAccessControlList} for the given path.<br>
+     *
+     * Note that the policy must be {@link AccessControlManager#setPolicy(String,
+     * javax.jcr.security.AccessControlPolicy) reapplied}
+     * and the changes must be saved in order to make the AC modifications take
+     * effect.
+     *
+     * @param accessControlManager The {@code AccessControlManager} .
+     * @param absPath The absolute path of the target node.
+     * @return A modifiable access control list or null if there is none.
+     * @throws RepositoryException If an error occurs.
+     */
+    public static JackrabbitAccessControlList getAccessControlList(AccessControlManager accessControlManager, String absPath) throws RepositoryException {
+        // try applicable (new) ACLs
+        AccessControlPolicyIterator itr = accessControlManager.getApplicablePolicies(absPath);
+        while (itr.hasNext()) {
+            AccessControlPolicy policy = itr.nextAccessControlPolicy();
+            if (policy instanceof JackrabbitAccessControlList) {
+                return (JackrabbitAccessControlList) policy;
+            }
+        }
+
+        // try if there is an acl that has been set before
+        AccessControlPolicy[] pcls = accessControlManager.getPolicies(absPath);
+        for (AccessControlPolicy policy : pcls) {
+            if (policy instanceof JackrabbitAccessControlList) {
+                return (JackrabbitAccessControlList) policy;
+            }
+        }
+
+        // no policy found
+        return null;
+    }
+
+    /**
+     * A utility method to add a new access control entry.<br>
+     * Please note, that calling {@link javax.jcr.Session#save()()} is required
+     * in order to persist the changes.
+     *
+     * @param session The editing session.
+     * @param absPath The absolute path of the target node.
+     * @param principal The principal to grant/deny privileges to.
+     * @param privilegeNames The names of the privileges to grant or deny.
+     * @param isAllow {@code true} to grant; {@code false} otherwise.
+     * @return {@code true} if the node's ACL was modified and the session has
+     * pending changes.
+     * @throws RepositoryException If an error occurs.
+     */
+    public static boolean addAccessControlEntry(Session session, String absPath,
+                                                Principal principal, String[] privilegeNames,
+                                                boolean isAllow) throws RepositoryException {
+        return addAccessControlEntry(session, absPath, principal, privilegesFromNames(session, privilegeNames), isAllow);
+    }
+
+    /**
+     * A utility method to add a new access control entry. Please note, that
+     * a call to {@link javax.jcr.Session#save()()} is required in order
+     * to persist the changes.
+     *
+     * @param session The editing session
+     * @param absPath The absolute path of the target node.
+     * @param principal The principal to grant/deny privileges to.
+     * @param privileges The privileges to grant or deny
+     * @param isAllow {@code true} to grant; {@code false} otherwise;
+     * @return {@code true} if the node's ACL was modified and the session has
+     * pending changes.
+     * @throws RepositoryException If an error occurs.
+     */
+    public static boolean addAccessControlEntry(Session session, String absPath,
+                                                Principal principal, Privilege[] privileges,
+                                                boolean isAllow) throws RepositoryException {
+        JackrabbitAccessControlList acl = getAccessControlList(session, absPath);
+        if (acl != null) {
+            if (acl.addEntry(principal, privileges, isAllow)) {
+                session.getAccessControlManager().setPolicy(absPath, acl);
+                return true;
+            } // else: not modified
+        } // else: no acl found.
+
+        return false;
+    }
+
+    /**
+     * Utility to grant jcr:all privilege to the everyone group principal.
+     * Please note, that {@link javax.jcr.Session#save()()} is required in order
+     * to persist the changes.
+     *
+     * @param session The editing session.
+     * @param absPath The absolute path of the target node
+     * @return {@code true} if the node's access control list was modified;
+     * {@code false} otherwise;
+     * @throws RepositoryException If an error occurs.
+     */
+    public static boolean grantAllToEveryone(Session session, String absPath) throws RepositoryException {
+        Principal everyone = getEveryonePrincipal(session);
+        Privilege[] privileges = privilegesFromNames(session, Privilege.JCR_ALL);
+        return addAccessControlEntry(session, absPath, everyone, privileges, true);
+    }
+
+    /**
+     * Utility to deny jcr:all privilege to the everyone group principal.
+     * Please note, that {@link javax.jcr.Session#save()()} is required in order
+     * to persist the changes.
+     *
+     * @param session The editing session.
+     * @param absPath The absolute path of the target node
+     * @return {@code true} if the node's access control list was modified;
+     * {@code false} otherwise;
+     * @throws RepositoryException If an error occurs.
+     */
+    public static boolean denyAllToEveryone(Session session, String absPath) throws RepositoryException {
+        Principal everyone = getEveryonePrincipal(session);
+        Privilege[] privileges = privilegesFromNames(session, Privilege.JCR_ALL);
+        return addAccessControlEntry(session, absPath, everyone, privileges, false);
+    }
+
+    /**
+     * <b>Allow</b> certain privileges on a given node for a given principal.
+     *
+     * <p>To activate the ACL change, session.save() must be called.</p>
+     *
+     * @param node node to set the resource-based ACL entry on; underlying session is used to write the ACL
+     * @param principalName Name of the principal for which the ACL entry should apply
+     * @param privileges list of privileges to set by name (see {@link javax.jcr.security.Privilege})
+     * @return {@code true} if the node's ACL was modified and the session has pending changes.
+     * @throws RepositoryException If an unexpected repository error occurs
+     */
+    public static boolean allow(Node node, String principalName, String... privileges) throws RepositoryException {
+        return addAccessControlEntry(
+            node.getSession(),
+            node.getPath(),
+            getPrincipal(node.getSession(), principalName),
+            privileges,
+            true // allow
+        );
+    }
+
+    /**
+     * <b>Deny</b> certain privileges on a node for a given principal.
+     *
+     * <p>To activate the ACL change, session.save() must be called.</p>
+     *
+     * @param node node to set the resource-based ACL entry on; underlying session is used to write the ACL
+     * @param principalName Name of the principal for which the ACL entry should apply
+     * @param privileges list of privileges to set by name (see {@link javax.jcr.security.Privilege})
+     * @return {@code true} if the node's ACL was modified and the session has pending changes.
+     * @throws RepositoryException If an unexpected repository error occurs
+     */
+    public static boolean deny(Node node, String principalName, String... privileges) throws RepositoryException {
+        return addAccessControlEntry(
+            node.getSession(),
+            node.getPath(),
+            getPrincipal(node.getSession(), principalName),
+            privileges,
+            false // deny
+        );
+    }
+
+    /**
+     * Removes all ACL entries for a principal at a given absolute path. If the specified
+     * {@code principalName} is {@code null} the policy will be removed altogether.
+     * <p>Modifications only take effect upon {@code Session.save()}.</p>
+     *
+     * @param session The editing session.
+     * @param absPath Absolute path of an existing node from which to remove ACL entries (or the policy)
+     * @param principalName Name of the principal whose entries should be removed;
+     * use {@code null} to clear the policy.
+     * @return {@code true} if the policy has been modified; {@code false} otherwise.
+     * @throws RepositoryException If an unexpected repository error occurs
+     */
+    public static boolean clear(Session session, String absPath, String principalName) throws RepositoryException {
+        AccessControlManager acm = session.getAccessControlManager();
+        JackrabbitAccessControlList acl = null;
+        // only clear if there is an existing acl (no need to retrieve applicable policies)
+        AccessControlPolicy[] pcls = acm.getPolicies(absPath);
+        for (AccessControlPolicy policy : pcls) {
+            if (policy instanceof JackrabbitAccessControlList) {
+                acl = (JackrabbitAccessControlList) policy;
+            }
+        }
+        if (acl != null) {
+            if (principalName == null) {
+                acm.removePolicy(absPath, acl);
+                return true;
+            } else {
+                Principal principal = getPrincipal(session, principalName);
+                if (principal == null) {
+                    return false;
+                }
+                boolean removedEntries = false;
+                // remove all existing entries for principal
+                for (AccessControlEntry ace : acl.getAccessControlEntries()) {
+                    if (ace.getPrincipal().equals(principal)) {
+                        acl.removeAccessControlEntry(ace);
+                        removedEntries = true;
+                    }
+                }
+                if (removedEntries) {
+                    acm.setPolicy(absPath, acl);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Removes all ACL entries for a principal on a given node.
+     *
+     * <p>Modification to the policy only take effect upon {@code Session.save()} must be called.</p>
+     *
+     * @param node node from which to remove ACL entries; underlying session is used to write the changes
+     * @param principalName Name of the principal whose entries should be removed; use {@code null} to clear the policy altogether.
+     * @return {@code true} if the node's ACL was modified, {@code false} otherwise.
+     * @throws RepositoryException If an unexpected repository error occurs
+     */
+    public static boolean clear(Node node, String principalName) throws RepositoryException {
+        return clear(node.getSession(), node.getPath(), principalName);
+    }
+
+    /**
+     * Removes the access control list at a given node.
+     * <p>To persist the modifications, {@code Session.save()} must be called.</p>
+     *
+     * @param node node from which to remove the ACL; underlying session is used to write the changes
+     * @return {@code true} if the node's ACL was removed, {@code false} otherwise.
+     * @throws RepositoryException If an unexpected repository error occurs
+     */
+    public static boolean clear(Node node) throws RepositoryException {
+        return clear(node, null);
+    }
+
+    /**
+     * Removes the access control list at the specified absolute path.
+     * <p>To persist the modification, session.save() must be called.</p>
+     *
+     * @param session The editing session.
+     * @param absPath An absolute path of a valid node accessible to the editing session from which to remove the ACL.
+     * @return {@code true} if the node's ACL got removed, {@code false} otherwise.
+     * @throws RepositoryException If an unexpected repository error occurs
+     */
+    public static boolean clear(Session session, String absPath) throws RepositoryException {
+        return clear(session, absPath, null);
+    }
+
+    /**
+     * Retrieves the principal with the specified {@code principalName}. Shortcut
+     * for calling {@link PrincipalManager#getPrincipal(String)}.
+     *
+     * @param session The editing session which must be a {@code JackrabbitSession}.
+     * @param principalName The name of the principal.
+     * @return The principal with the specified name or {@code null} if no such principal exists.
+     * @throws RepositoryException If an error occurs or if the session is not a {@code JackrabbitSession}.
+     */
+    public static Principal getPrincipal(Session session, String principalName) throws RepositoryException {
+        if (session instanceof JackrabbitSession) {
+            return ((JackrabbitSession) session).getPrincipalManager().getPrincipal(principalName);
+        } else {
+            throw new UnsupportedOperationException("Failed to retrieve principal: JackrabbitSession expected.");
+        }
+    }
+
+    /**
+     * Shortcut for calling {@link PrincipalManager#getEveryone()}.
+     *
+     * @param session The editing session which must be a {@code JackrabbitSession}.
+     * @return The group principal presenting everyone.
+     * @throws RepositoryException If an error occurs or if the session is not a {@code JackrabbitSession}.
+     */
+    public static Principal getEveryonePrincipal(Session session) throws RepositoryException {
+        if (session instanceof JackrabbitSession) {
+            return ((JackrabbitSession) session).getPrincipalManager().getEveryone();
+        } else {
+            throw new UnsupportedOperationException("Failed to retrieve everyone principal: JackrabbitSession expected.");
+        }
+    }
+}
+
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/jackrabbit/user/AuthorizableQueryManager.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/jackrabbit/user/AuthorizableQueryManager.java
index 471828d..4d07635 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/jackrabbit/user/AuthorizableQueryManager.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/jackrabbit/user/AuthorizableQueryManager.java
@@ -60,6 +60,7 @@ import java.util.Stack;
     {
       property: /* relative path (String) * /
       ( direction: "asc" | "desc" )                       // Defaults to "asc"
+      ( ignoreCase: true | false )                        // Defaults to "true", see QueryBuilder#setSortOrder()
     }
   ) ?                                                     // Defaults to document order
 
@@ -636,15 +637,16 @@ public class AuthorizableQueryManager {
             private String currentKey;
             private String property;
             private QueryBuilder.Direction direction;
+            private boolean ignoreCase = true;
 
             @Override
             public void endObject() throws IOException {
                 if (property == null) {
                     throw new IOException("Missing property");
                 } else {
-                    queryBuilder.setSortOrder(property, direction == null
-                            ? QueryBuilder.Direction.ASCENDING
-                            : direction, true);
+                    queryBuilder.setSortOrder(property,
+                            direction == null ? QueryBuilder.Direction.ASCENDING : direction,
+                            ignoreCase);
                 }
                 handlers.pop();
             }
@@ -660,6 +662,8 @@ public class AuthorizableQueryManager {
                     property = s;
                 } else if ("direction".equals(currentKey)) {
                     direction = directionFor(s);
+                } else if ("ignoreCase".equals(currentKey)) {
+                    ignoreCase = Boolean.valueOf(s);
                 } else {
                     throw new IOException("Unexpected: '" + currentKey + ':' + s + '\'');
                 }
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/observation/EventTracker.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/observation/EventTracker.java
new file mode 100644
index 0000000..ad0304a
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/observation/EventTracker.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.commons.observation;
+
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.observation.Event;
+
+/**
+ * Event decorator that tracks whether user and date information is being
+ * accessed from external events or without checking for externality.
+ *
+ * @see ListenerTracker
+ */
+class EventTracker implements Event {
+
+    /** The enclosing listener tracker */
+    private final ListenerTracker listener;
+
+    protected final Event event;
+
+    protected final AtomicBoolean externalAccessed =
+            new AtomicBoolean();
+
+    public EventTracker(ListenerTracker listenerTracker, Event event) {
+        listener = listenerTracker;
+        this.event = event;
+    }
+
+    private void userInfoAccessed() {
+        if (!externalAccessed.get()
+                && !listener.userInfoAccessedWithoutExternalsCheck.getAndSet(true)) {
+            listener.warn("Event listener " + listener + " is trying"
+                    + " to access user information of event " + event
+                    + " without checking whether the event is external.");
+        }
+        if (eventIsExternal()
+                && !listener.userInfoAccessedFromExternalEvent.getAndSet(true)) {
+            listener.warn("Event listener " + listener + " is trying"
+                    + " to access user information of external event "
+                    + event + ".");
+        }
+    }
+
+    private void dateInfoAccessed() {
+        if (!externalAccessed.get()
+                && !listener.dateAccessedWithoutExternalsCheck.getAndSet(true)) {
+            listener.warn("Event listener " + listener + " is trying"
+                    + " to access date information of event " + event
+                    + " without checking whether the event is external.");
+        }
+        if (eventIsExternal()
+                && !listener.dateAccessedFromExternalEvent.getAndSet(true)) {
+            listener.warn("Event listener " + listener + " is trying"
+                    + " to access date information of external event "
+                    + event + ".");
+        }
+    }
+
+    protected boolean eventIsExternal() {
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return event.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return event.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        } else if (other instanceof EventTracker) {
+            return event.equals(other);
+        } else {
+            return false;
+        }
+    }
+
+    //---------------------------------------------------------< Event >--
+
+    @Override
+    public int getType() {
+        return event.getType();
+    }
+
+    @Override
+    public String getPath() throws RepositoryException {
+        return event.getPath();
+    }
+
+    @Override
+    public String getUserID() {
+        userInfoAccessed();
+        return event.getUserID();
+    }
+
+    @Override
+    public String getIdentifier() throws RepositoryException {
+        return event.getIdentifier();
+    }
+
+    @Override
+    public Map<?, ?> getInfo() throws RepositoryException {
+        return event.getInfo();
+    }
+
+    @Override
+    public String getUserData() throws RepositoryException {
+        userInfoAccessed();
+        return event.getUserData();
+    }
+
+    @Override
+    public long getDate() throws RepositoryException {
+        dateInfoAccessed();
+        return event.getDate();
+    }
+
+}
\ No newline at end of file
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/observation/JackrabbitEventTracker.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/observation/JackrabbitEventTracker.java
new file mode 100644
index 0000000..928408b
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/observation/JackrabbitEventTracker.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.commons.observation;
+
+import javax.jcr.observation.Event;
+
+import org.apache.jackrabbit.api.observation.JackrabbitEvent;
+
+class JackrabbitEventTracker extends EventTracker
+        implements JackrabbitEvent {
+
+    public JackrabbitEventTracker(ListenerTracker listener, Event event) {
+        super(listener, event);
+    }
+
+    @Override
+    protected boolean eventIsExternal() {
+        return ((JackrabbitEvent) event).isExternal();
+    }
+
+    //----------------------------------------------------< JackrabbitEvent>--
+
+    @Override
+    public boolean isExternal() {
+        externalAccessed.set(true);
+        return eventIsExternal();
+    }
+
+}
\ No newline at end of file
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/observation/ListenerTracker.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/observation/ListenerTracker.java
new file mode 100644
index 0000000..041261a
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/observation/ListenerTracker.java
@@ -0,0 +1,365 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.commons.observation;
+
+import static java.lang.System.currentTimeMillis;
+import static java.lang.System.nanoTime;
+import static org.apache.jackrabbit.stats.TimeSeriesStatsUtil.asCompositeData;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.EventListener;
+import javax.management.openmbean.CompositeData;
+
+import org.apache.jackrabbit.api.jmx.EventListenerMBean;
+import org.apache.jackrabbit.api.observation.JackrabbitEvent;
+import org.apache.jackrabbit.commons.iterator.EventIteratorAdapter;
+import org.apache.jackrabbit.stats.TimeSeriesMax;
+import org.apache.jackrabbit.stats.TimeSeriesRecorder;
+
+/**
+ * Tracks event deliveries to an event listener and the way the listener
+ * processes the events. The collected information is made available through
+ * the {@link EventListenerMBean} interface.
+ */
+public class ListenerTracker {
+
+    private final EventListener listener;
+
+    private final int eventTypes;
+
+    private final String absPath;
+
+    private final boolean isDeep;
+
+    private final String[] uuid;
+
+    private final String[] nodeTypeName;
+
+    private final boolean noLocal;
+
+    protected final Exception initStackTrace =
+            new Exception("The event listener was registered here:");
+
+    private final long startTime = currentTimeMillis();
+
+    private final AtomicLong eventDeliveries = new AtomicLong();
+
+    private final AtomicLong eventsDelivered = new AtomicLong();
+
+    private final AtomicLong eventDeliveryTime = new AtomicLong();
+
+    private final TimeSeriesMax queueLength = new TimeSeriesMax();
+
+    private final TimeSeriesRecorder eventCount = new TimeSeriesRecorder(true);
+
+    private final TimeSeriesRecorder eventConsumerTime = new TimeSeriesRecorder(true);
+
+    private final TimeSeriesRecorder eventProducerTime = new TimeSeriesRecorder(true);
+
+    final AtomicBoolean userInfoAccessedWithoutExternalsCheck =
+            new AtomicBoolean();
+
+    final AtomicBoolean userInfoAccessedFromExternalEvent =
+            new AtomicBoolean();
+
+    final AtomicBoolean dateAccessedWithoutExternalsCheck =
+            new AtomicBoolean();
+
+    final AtomicBoolean dateAccessedFromExternalEvent =
+            new AtomicBoolean();
+
+    public ListenerTracker(
+            EventListener listener, int eventTypes, String absPath, boolean isDeep, String[] uuid,
+            String[] nodeTypeName, boolean noLocal) {
+        this.listener = listener;
+        this.eventTypes = eventTypes;
+        this.absPath = absPath;
+        this.isDeep = isDeep;
+        this.uuid = copy(uuid);
+        this.nodeTypeName = copy(nodeTypeName);
+        this.noLocal = noLocal;
+    }
+
+    /**
+     * Called to log a deprecation warning about the detected behavior of
+     * the decorated listener. Subclasses should override this method that
+     * by default does nothing.
+     *
+     * @param message warning message
+     */
+    protected void warn(String message) {
+        // do nothing
+    }
+
+    /**
+     * Called just before the {@link EventListener#onEvent(EventIterator)}
+     * method is called. The default implementation of this method does
+     * nothing, but subclasses can override it to add custom processing.
+     */
+    protected void beforeEventDelivery() {
+        // do nothing
+    }
+
+    /**
+     * Called just after the {@link EventListener#onEvent(EventIterator)}
+     * method has been called (even if the call threw an exception). The
+     * default implementation of this method does nothing, but subclasses
+     * can override it to add custom processing.
+     */
+    protected void afterEventDelivery() {
+        // do nothing
+    }
+
+    /**
+     * Applications should call this to report the current queue length.
+     * @param length
+     */
+    public void recordQueueLength(long length) {
+        queueLength.recordValue(length);
+    }
+
+    /**
+     * Records the number of measured values over the past second and resets
+     * the counter. This method should be scheduled to be called once per
+     * second.
+     */
+    public void recordOneSecond() {
+        queueLength.recordOneSecond();
+        eventCount.recordOneSecond();
+        eventConsumerTime.recordOneSecond();
+        eventProducerTime.recordOneSecond();
+    }
+
+    public EventListener getTrackedListener() {
+        return new EventListener() {
+            @Override
+            public void onEvent(EventIterator events) {
+                eventDeliveries.incrementAndGet();
+                final long start = nanoTime();
+                try {
+                    beforeEventDelivery();
+                    listener.onEvent(new EventIteratorAdapter(events) {
+                        long t0 = start;
+
+                        private void recordTime(TimeSeriesRecorder recorder) {
+                            recorder.getCounter().addAndGet(-(t0 - (t0 = nanoTime())));
+                        }
+
+                        @Override
+                        public Object next() {
+                            recordTime(eventConsumerTime);
+                            eventsDelivered.incrementAndGet();
+                            eventCount.getCounter().incrementAndGet();
+                            Object object = super.next();
+                            if (object instanceof JackrabbitEvent) {
+                                object = new JackrabbitEventTracker(
+                                        ListenerTracker.this,
+                                        (JackrabbitEvent) object);
+                            } else if (object instanceof Event) {
+                                object = new EventTracker(
+                                        ListenerTracker.this, (Event) object);
+                            }
+                            recordTime(eventProducerTime);
+                            return object;
+                        }
+
+                        @Override
+                        public boolean hasNext() {
+                            recordTime(eventConsumerTime);
+                            boolean result = super.hasNext();
+                            t0 = nanoTime();
+                            recordTime(eventProducerTime);
+                            return result;
+                        }
+                    });
+                } finally {
+                    afterEventDelivery();
+                    eventDeliveryTime.addAndGet(nanoTime() - start);
+                }
+            }
+
+            @Override
+            public String toString() {
+                return ListenerTracker.this.toString();
+            }
+        };
+    }
+
+    public EventListenerMBean getListenerMBean() {
+        return new EventListenerMBean() {
+            @Override
+            public String getClassName() {
+                return listener.getClass().getName();
+            }
+            @Override
+            public String getInitStackTrace() {
+                StringWriter writer = new StringWriter();
+                initStackTrace.printStackTrace(new PrintWriter(writer));
+                return writer.toString();
+            }
+            @Override
+            public int getEventTypes() {
+                return eventTypes;
+            }
+            @Override
+            public String getAbsPath() {
+                return absPath;
+            }
+            @Override
+            public boolean isDeep() {
+                return isDeep;
+            }
+            @Override
+            public String[] getUuid() {
+                return copy(uuid);
+            }
+            @Override
+            public String[] getNodeTypeName() {
+                return copy(nodeTypeName);
+            }
+            @Override
+            public boolean isNoLocal() {
+                return noLocal;
+            }
+            @Override
+            public long getEventDeliveries() {
+                return eventDeliveries.get();
+            }
+            @Override
+            public long getEventDeliveriesPerHour() {
+                return TimeUnit.HOURS.toMillis(getEventDeliveries())
+                        / Math.max(currentTimeMillis() - startTime, 1);
+            }
+            @Override
+            public long getMicrosecondsPerEventDelivery() {
+                return TimeUnit.NANOSECONDS.toMicros(eventDeliveryTime.get())
+                        / Math.max(getEventDeliveries(), 1);
+            }
+            @Override
+            public long getEventsDelivered() {
+                return eventsDelivered.get();
+            }
+            @Override
+            public long getEventsDeliveredPerHour() {
+                return TimeUnit.HOURS.toMillis(getEventsDelivered())
+                        / Math.max(currentTimeMillis() - startTime, 1);
+            }
+            @Override
+            public long getMicrosecondsPerEventDelivered() {
+                return TimeUnit.NANOSECONDS.toMicros(eventDeliveryTime.get())
+                        / Math.max(getEventsDelivered(), 1);
+            }
+            @Override
+            public double getRatioOfTimeSpentProcessingEvents() {
+                double timeSpentProcessingEvents =
+                        TimeUnit.NANOSECONDS.toMillis(eventDeliveryTime.get());
+                return timeSpentProcessingEvents
+                        / Math.max(currentTimeMillis() - startTime, 1);
+            }
+            @Override
+            public boolean isUserInfoAccessedWithoutExternalsCheck() {
+                return userInfoAccessedWithoutExternalsCheck.get();
+            }
+            @Override
+            public synchronized boolean isUserInfoAccessedFromExternalEvent() {
+                return userInfoAccessedFromExternalEvent.get();
+            }
+            @Override
+            public synchronized boolean isDateAccessedWithoutExternalsCheck() {
+                return dateAccessedWithoutExternalsCheck.get();
+            }
+            @Override
+            public synchronized boolean isDateAccessedFromExternalEvent() {
+                return dateAccessedFromExternalEvent.get();
+            }
+            @Override
+            public CompositeData getQueueLength() {
+                return asCompositeData(queueLength, "queueLength");
+            }
+            @Override
+            public CompositeData getEventCount() {
+                return asCompositeData(eventCount, "eventCount");
+            }
+            @Override
+            public CompositeData getEventConsumerTime() {
+                return asCompositeData(eventConsumerTime, "eventConsumerTime");
+            }
+            @Override
+            public CompositeData getEventProducerTime() {
+                return asCompositeData(eventProducerTime, "eventProducerTime");
+            }
+        };
+    }
+
+    //------------------------------------------------------------< Object >--
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        if (absPath != null) {
+            builder.append(absPath);
+        }
+        if (isDeep) {
+            builder.append("//*");
+        } else {
+            builder.append("/*");
+        }
+        builder.append('[');
+        builder.append(Integer.toBinaryString(eventTypes));
+        builder.append('b');
+        if (uuid != null) {
+            for (String id : uuid) {
+                builder.append(", ");
+                builder.append(id);
+            }
+        }
+        if (nodeTypeName != null) {
+            for (String name : nodeTypeName) {
+                builder.append(", ");
+                builder.append(name);
+            }
+        }
+        if (noLocal) {
+            builder.append(", no local");
+        }
+        builder.append("]@");
+        builder.append(listener.getClass().getName());
+        return builder.toString();
+    }
+
+    //-----------------------------------------------------------< private >--
+
+    private static String[] copy(String[] array) {
+        if (array != null && array.length > 0) {
+            String[] copy = new String[array.length];
+            System.arraycopy(array, 0, copy, 0, array.length);
+            return copy;
+        } else {
+            return array;
+        }
+    }
+
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/package-info.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/package-info.java
index 2b64ed0..8e1afdf 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/package-info.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/package-info.java
@@ -14,5 +14,5 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- at aQute.bnd.annotation.Version("2.3.2")
+ at aQute.bnd.annotation.Version("2.4.0")
 package org.apache.jackrabbit.commons;
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/packaging/ContentPackage.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/packaging/ContentPackage.java
index 97684fa..dea1379 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/packaging/ContentPackage.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/packaging/ContentPackage.java
@@ -1,27 +1,27 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.commons.packaging;
-
-import java.util.Iterator;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-public interface ContentPackage {
-
-    Iterator getItems(Session session) throws RepositoryException;
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.commons.packaging;
+
+import java.util.Iterator;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+public interface ContentPackage {
+
+    Iterator getItems(Session session) throws RepositoryException;
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/packaging/ContentPackageExporter.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/packaging/ContentPackageExporter.java
index f3c5143..ef4ff5d 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/packaging/ContentPackageExporter.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/packaging/ContentPackageExporter.java
@@ -1,27 +1,27 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.commons.packaging;
-
-import java.io.OutputStream;
-
-import javax.jcr.RepositoryException;
-
-public interface ContentPackageExporter {
-
-    void export(ContentPackage description, OutputStream out)
-    throws RepositoryException;
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.commons.packaging;
+
+import java.io.OutputStream;
+
+import javax.jcr.RepositoryException;
+
+public interface ContentPackageExporter {
+
+    void export(ContentPackage description, OutputStream out)
+    throws RepositoryException;
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/packaging/FilterContentPackage.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/packaging/FilterContentPackage.java
index 048e389..a33df63 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/packaging/FilterContentPackage.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/packaging/FilterContentPackage.java
@@ -1,205 +1,205 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.commons.packaging;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-
-import javax.jcr.Item;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.PropertyIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-import org.apache.jackrabbit.commons.predicate.Predicate;
-
-public class FilterContentPackage implements ContentPackage {
-
-    protected final List<Content> content = new ArrayList<Content>();
-
-    protected boolean includeProperties = false;
-
-    public void addContent(String path, Predicate filterList) {
-        this.content.add(new Content(new String[] {path}, filterList));
-    }
-
-    public void addContent(String[] paths, Predicate filterList) {
-        this.content.add(new Content(paths, filterList));
-    }
-
-    /**
-     * @see org.apache.jackrabbit.commons.packaging.ContentPackage#getItems(javax.jcr.Session)
-     */
-    public Iterator<Item> getItems(Session session)
-    throws RepositoryException {
-        return new FilteringIterator(session, new ArrayList<Content>(this.content), this.includeProperties);
-    }
-
-    protected static class Content {
-        protected final String[] paths;
-        protected final Predicate filterList;
-
-        public Content(String[] paths, Predicate filterList) {
-            this.paths = paths;
-            this.filterList = filterList;
-        }
-    }
-
-    public static class FilteringIterator implements Iterator {
-
-        /** The content we will iterate over. */
-        protected final List<Content> content;
-
-        /**
-         * Filter that defines which items are included
-         */
-        protected Predicate includeFilter;
-
-        protected int contentIndex, pathIndex;
-
-        protected Item nextItem;
-
-        protected Node lastNode;
-
-        protected final Session session;
-
-        protected final List<NodeIterator> nodeIteratorStack = new ArrayList<NodeIterator>();
-
-        protected final boolean includeProperties;
-
-        protected PropertyIterator propertyIterator;
-
-        /**
-         * Creates a new tree walker that uses the given filter as include and
-         * traversal filter.
-         *
-         * @param session The session.
-         * @param contentList The list of content objects.
-         * @param includeProperties Should properties be included.
-         */
-        public FilteringIterator(final Session session,
-                                 final List<Content> contentList,
-                                 final boolean includeProperties) {
-            this.content = contentList;
-            this.session = session;
-            this.includeProperties = includeProperties;
-        }
-
-        /**
-         * @see java.util.Iterator#hasNext()
-         */
-        public boolean hasNext() {
-            if ( this.nextItem != null ) {
-                return true;
-            }
-            try {
-                return this.checkForNextNode();
-            } catch (RepositoryException e) {
-                // if any error occurs, we stop iterating
-                return false;
-            }
-        }
-
-        protected boolean checkForNextNode() throws RepositoryException {
-            if ( this.propertyIterator != null ) {
-                if ( this.propertyIterator.hasNext() ) {
-                    this.nextItem = this.propertyIterator.nextProperty();
-                    return true;
-                }
-                this.propertyIterator = null;
-            } else if ( this.includeProperties && this.lastNode != null ) {
-                if ( this.lastNode.hasProperties() ) {
-                    this.propertyIterator = this.lastNode.getProperties();
-                    this.propertyIterator.hasNext();
-                    this.nextItem = this.propertyIterator.nextProperty();
-                    return true;
-                }
-            }
-            if ( this.lastNode != null ) {
-
-                if ( this.lastNode.hasNodes() ) {
-                    final NodeIterator iter = this.lastNode.getNodes();
-                    this.nodeIteratorStack.add(iter);
-                }
-                while ( this.nodeIteratorStack.size() > 0 ) {
-                    final NodeIterator iter = (NodeIterator)this.nodeIteratorStack.get(this.nodeIteratorStack.size() - 1);
-                    if ( iter.hasNext() ) {
-                        do {
-                            final Node contextNode = iter.nextNode();
-                            if ( this.includeFilter.evaluate(contextNode) ) {
-                                this.lastNode = contextNode;
-                                this.nextItem = contextNode;
-                                return true;
-                            }
-                        } while ( iter.hasNext() );
-                    }
-                    this.nodeIteratorStack.remove(iter);
-                }
-                this.pathIndex++;
-                this.lastNode = null;
-            }
-            while ( this.contentIndex < this.content.size() ) {
-                final Content content = (Content)this.content.get(this.contentIndex);
-                this.includeFilter = content.filterList;
-                while ( this.pathIndex < content.paths.length ) {
-                    final String path = content.paths[this.pathIndex];
-                    this.pathIndex++;
-                    final Node contextNode = (Node)this.session.getItem(path);
-                    if ( this.includeFilter.evaluate(contextNode) ) {
-                        this.lastNode = contextNode;
-                        this.nextItem = contextNode;
-                        return true;
-                    }
-                }
-                this.contentIndex++;
-                this.pathIndex = 0;
-            }
-
-            return false;
-        }
-
-        /**
-         * @see java.util.Iterator#next()
-         */
-        public Object next() {
-            if ( this.hasNext() ) {
-                final Item result = nextItem;
-                this.nextItem = null;
-                return result;
-            }
-            throw new NoSuchElementException("No more elements available");
-        }
-
-        /**
-         * @see java.util.Iterator#remove()
-         */
-        public void remove() {
-            throw new UnsupportedOperationException("Remove is not supported.");
-        }
-    }
-
-    public boolean isIncludeProperties() {
-        return includeProperties;
-    }
-
-    public void setIncludeProperties(boolean includeProperties) {
-        this.includeProperties = includeProperties;
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.commons.packaging;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.commons.predicate.Predicate;
+
+public class FilterContentPackage implements ContentPackage {
+
+    protected final List<Content> content = new ArrayList<Content>();
+
+    protected boolean includeProperties = false;
+
+    public void addContent(String path, Predicate filterList) {
+        this.content.add(new Content(new String[] {path}, filterList));
+    }
+
+    public void addContent(String[] paths, Predicate filterList) {
+        this.content.add(new Content(paths, filterList));
+    }
+
+    /**
+     * @see org.apache.jackrabbit.commons.packaging.ContentPackage#getItems(javax.jcr.Session)
+     */
+    public Iterator<Item> getItems(Session session)
+    throws RepositoryException {
+        return new FilteringIterator(session, new ArrayList<Content>(this.content), this.includeProperties);
+    }
+
+    protected static class Content {
+        protected final String[] paths;
+        protected final Predicate filterList;
+
+        public Content(String[] paths, Predicate filterList) {
+            this.paths = paths;
+            this.filterList = filterList;
+        }
+    }
+
+    public static class FilteringIterator implements Iterator {
+
+        /** The content we will iterate over. */
+        protected final List<Content> content;
+
+        /**
+         * Filter that defines which items are included
+         */
+        protected Predicate includeFilter;
+
+        protected int contentIndex, pathIndex;
+
+        protected Item nextItem;
+
+        protected Node lastNode;
+
+        protected final Session session;
+
+        protected final List<NodeIterator> nodeIteratorStack = new ArrayList<NodeIterator>();
+
+        protected final boolean includeProperties;
+
+        protected PropertyIterator propertyIterator;
+
+        /**
+         * Creates a new tree walker that uses the given filter as include and
+         * traversal filter.
+         *
+         * @param session The session.
+         * @param contentList The list of content objects.
+         * @param includeProperties Should properties be included.
+         */
+        public FilteringIterator(final Session session,
+                                 final List<Content> contentList,
+                                 final boolean includeProperties) {
+            this.content = contentList;
+            this.session = session;
+            this.includeProperties = includeProperties;
+        }
+
+        /**
+         * @see java.util.Iterator#hasNext()
+         */
+        public boolean hasNext() {
+            if ( this.nextItem != null ) {
+                return true;
+            }
+            try {
+                return this.checkForNextNode();
+            } catch (RepositoryException e) {
+                // if any error occurs, we stop iterating
+                return false;
+            }
+        }
+
+        protected boolean checkForNextNode() throws RepositoryException {
+            if ( this.propertyIterator != null ) {
+                if ( this.propertyIterator.hasNext() ) {
+                    this.nextItem = this.propertyIterator.nextProperty();
+                    return true;
+                }
+                this.propertyIterator = null;
+            } else if ( this.includeProperties && this.lastNode != null ) {
+                if ( this.lastNode.hasProperties() ) {
+                    this.propertyIterator = this.lastNode.getProperties();
+                    this.propertyIterator.hasNext();
+                    this.nextItem = this.propertyIterator.nextProperty();
+                    return true;
+                }
+            }
+            if ( this.lastNode != null ) {
+
+                if ( this.lastNode.hasNodes() ) {
+                    final NodeIterator iter = this.lastNode.getNodes();
+                    this.nodeIteratorStack.add(iter);
+                }
+                while ( this.nodeIteratorStack.size() > 0 ) {
+                    final NodeIterator iter = (NodeIterator)this.nodeIteratorStack.get(this.nodeIteratorStack.size() - 1);
+                    if ( iter.hasNext() ) {
+                        do {
+                            final Node contextNode = iter.nextNode();
+                            if ( this.includeFilter.evaluate(contextNode) ) {
+                                this.lastNode = contextNode;
+                                this.nextItem = contextNode;
+                                return true;
+                            }
+                        } while ( iter.hasNext() );
+                    }
+                    this.nodeIteratorStack.remove(iter);
+                }
+                this.pathIndex++;
+                this.lastNode = null;
+            }
+            while ( this.contentIndex < this.content.size() ) {
+                final Content content = (Content)this.content.get(this.contentIndex);
+                this.includeFilter = content.filterList;
+                while ( this.pathIndex < content.paths.length ) {
+                    final String path = content.paths[this.pathIndex];
+                    this.pathIndex++;
+                    final Node contextNode = (Node)this.session.getItem(path);
+                    if ( this.includeFilter.evaluate(contextNode) ) {
+                        this.lastNode = contextNode;
+                        this.nextItem = contextNode;
+                        return true;
+                    }
+                }
+                this.contentIndex++;
+                this.pathIndex = 0;
+            }
+
+            return false;
+        }
+
+        /**
+         * @see java.util.Iterator#next()
+         */
+        public Object next() {
+            if ( this.hasNext() ) {
+                final Item result = nextItem;
+                this.nextItem = null;
+                return result;
+            }
+            throw new NoSuchElementException("No more elements available");
+        }
+
+        /**
+         * @see java.util.Iterator#remove()
+         */
+        public void remove() {
+            throw new UnsupportedOperationException("Remove is not supported.");
+        }
+    }
+
+    public boolean isIncludeProperties() {
+        return includeProperties;
+    }
+
+    public void setIncludeProperties(boolean includeProperties) {
+        this.includeProperties = includeProperties;
+    }
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/packaging/package-info.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/packaging/package-info.java
index 9c3cd12..fc8c8d7 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/packaging/package-info.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/packaging/package-info.java
@@ -14,5 +14,5 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- at aQute.bnd.annotation.Version("2.2")
+ at aQute.bnd.annotation.Version("2.2.1")
 package org.apache.jackrabbit.commons.packaging;
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/query/GQL.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/query/GQL.java
index 99d56fb..e749e5b 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/query/GQL.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/query/GQL.java
@@ -48,7 +48,7 @@ import org.apache.jackrabbit.util.Text;
 /**
  * <code>GQL</code> is a simple fulltext query language, which supports field
  * prefixes similar to Lucene or Google queries.
- * <p/>
+ * <p>
  * GQL basically consists of a list of query terms that are optionally prefixed
  * with a property name. E.g.: <code>title:jackrabbit</code>. When a property
  * prefix is omitted, GQL will perform a fulltext search on all indexed
@@ -78,14 +78,14 @@ import org.apache.jackrabbit.util.Text;
  * The following wild cards are allowed: '*', matching any character sequence of
  * length 0..n; '?', matching any single character.</li>
  * </ul>
- * <p/>
+ * <p>
  * <b>Property name</b>
- * <p/>
+ * <p>
  * Instead of a property name you may also specify a relative path to a
  * property. E.g.: <code>"jcr:content/jcr:mimeType":text/plain</code>
- * <p/>
+ * <p>
  * <b>Double quotes</b>
- * <p/>
+ * <p>
  * The property name as well as the value may enclosed in double quotes. For
  * certain use cases this is required. E.g. if you want to search for a phrase:
  * <code>title:"apache jackrabbit"</code>. Similarly you need to enclose the
@@ -93,9 +93,9 @@ import org.apache.jackrabbit.util.Text;
  * otherwise the first colon is interpreted as the separator between the
  * property name and the value. This also means that a value that contains
  * a colon does not need to be enclosed in double quotes.
- * <p/>
+ * <p>
  * <b>Auto prefixes</b>
- * <p/>
+ * <p>
  * When a property, node or node type name does not have a namespace prefix GQL
  * will guess the prefix by looking up item and node type definitions in the
  * node type manager. If it finds a definition with a local name that matches
@@ -104,9 +104,9 @@ import org.apache.jackrabbit.util.Text;
  * of node type <code>nt:file</code>. Similarly you can write:
  * <code>order:lastModified</code> and your result nodes will be sorted by their
  * <code>jcr:lastModified</code> property value.
- * <p/>
+ * <p>
  * <b>Common path prefix</b>
- * <p/>
+ * <p>
  * For certain queries it is useful to specify a common path prefix for the
  * GQL query statement. See {@link #execute(String, Session, String)}. E.g. if
  * you are searching for file nodes with matches in their resource node. The
@@ -116,16 +116,16 @@ import org.apache.jackrabbit.util.Text;
  * where the third parameter is <code>jcr:content</code>. GQL will return
  * <code>nt:file</code> nodes with <code>jcr:content</code> nodes that contain
  * matches for <code>jackrabbit</code>.
- * <p/>
+ * <p>
  * <b>Excerpts</b>
- * <p/>
+ * <p>
  * To get an excerpt for the current row in the result set simply call
  * {@link Row#getValue(String) Row.getValue("rep:excerpt()");}. Please note
  * that this is feature is Jackrabbit specific and will not work with other
  * implementations!
- * <p/>
+ * <p>
  * <b>Parser callbacks</b>
- * <p/>
+ * <p>
  * You can get callbacks for each field and query term pair using the method
  * {@link #parse(String, Session, ParserCallback)}. This may be useful when you
  * want to do some transformation on the GQL before it is actually executed.
@@ -197,6 +197,11 @@ public final class GQL {
      * specific).
      */
     private static final String REP_EXCERPT = "rep:excerpt";
+    
+    /**
+     * A pseudo-property for native xpath conditions.
+     */
+    private static final String NATIVE_XPATH = "jcr:nativeXPath";
 
     /**
      * The GQL query statement.
@@ -321,6 +326,40 @@ public final class GQL {
         GQL query = new GQL(statement, session, commonPathPrefix, filter);
         return query.execute();
     }
+    
+    /**
+     * Executes the GQL query and returns the result as a row iterator.
+     *
+     * @param jcrQuery the native JCR query.
+     * @param jcrQueryLanguage the JCR query language
+     * @param session the session that will execute the query.
+     * @param commonPathPrefix a common path prefix for the GQL query.
+     * @param filter an optional filter that may include/exclude result rows.
+     * @return the result.
+     */
+    public static RowIterator executeXPath(String jcrQuery,
+                                      String jcrQueryLanguage,
+                                      Session session,
+                                      String commonPathPrefix,
+                                      Filter filter) {
+        GQL query = new GQL("", session, commonPathPrefix, filter);
+        return query.executeJcrQuery(jcrQuery, jcrQueryLanguage);
+    }
+    
+    /**
+     * Translate the GQL query to XPath.
+     *
+     * @param statement the GQL query.
+     * @param session the session that will execute the query.
+     * @param commonPathPrefix a common path prefix for the GQL query.
+     * @return the xpath statement.
+     */
+    public static String translateToXPath(String statement,
+            Session session,
+            String commonPathPrefix) throws RepositoryException {
+        GQL query = new GQL(statement, session, commonPathPrefix, null);
+        return query.translateStatement();
+    }
 
     /**
      * Parses the given <code>statement</code> and generates callbacks for each
@@ -385,10 +424,20 @@ public final class GQL {
      * @return the result.
      */
     private RowIterator execute() {
+        String xpath;
+        try {
+            xpath = translateStatement();
+        } catch (RepositoryException e) {
+            // in case of error return empty result
+            return RowIteratorAdapter.EMPTY;
+        }
+        return executeJcrQuery(xpath, Query.XPATH);
+    }
+    
+    private RowIterator executeJcrQuery(String jcrQuery, String jcrQueryLanguage) {
         try {
-            String stmt = translateStatement();
             QueryManager qm = session.getWorkspace().getQueryManager();
-            RowIterator nodes = qm.createQuery(stmt, Query.XPATH).execute().getRows();
+            RowIterator nodes = qm.createQuery(jcrQuery, jcrQueryLanguage).execute().getRows();
             if (filter != null) {
                 nodes = new FilteredRowIterator(nodes);
             }
@@ -410,7 +459,7 @@ public final class GQL {
         } catch (RepositoryException e) {
             // in case of error return empty result
             return RowIteratorAdapter.EMPTY;
-        }
+        }        
     }
 
     /**
@@ -572,20 +621,22 @@ public final class GQL {
         }
         if (propertyNames == null) {
             propertyNames = new HashMap<String, String>();
-            NodeTypeManager ntMgr = session.getWorkspace().getNodeTypeManager();
-            NodeTypeIterator it = ntMgr.getAllNodeTypes();
-            while (it.hasNext()) {
-                NodeType nt = it.nextNodeType();
-                PropertyDefinition[] defs = nt.getDeclaredPropertyDefinitions();
-                for (PropertyDefinition def : defs) {
-                    String pn = def.getName();
-                    if (!pn.equals("*")) {
-                        String localName = pn;
-                        int idx = pn.indexOf(':');
-                        if (idx != -1) {
-                            localName = pn.substring(idx + 1);
+            if (session != null) {
+                NodeTypeManager ntMgr = session.getWorkspace().getNodeTypeManager();
+                NodeTypeIterator it = ntMgr.getAllNodeTypes();
+                while (it.hasNext()) {
+                    NodeType nt = it.nextNodeType();
+                    PropertyDefinition[] defs = nt.getDeclaredPropertyDefinitions();
+                    for (PropertyDefinition def : defs) {
+                        String pn = def.getName();
+                        if (!pn.equals("*")) {
+                            String localName = pn;
+                            int idx = pn.indexOf(':');
+                            if (idx != -1) {
+                                localName = pn.substring(idx + 1);
+                            }
+                            propertyNames.put(localName, pn);
                         }
-                        propertyNames.put(localName, pn);
                     }
                 }
             }
@@ -923,6 +974,10 @@ public final class GQL {
 
         public void toString(StringBuffer buffer)
                 throws RepositoryException {
+            if (property.equals(NATIVE_XPATH)) {
+                buffer.append(value);
+                return;
+            }        
             if (prohibited) {
                 buffer.append("not(");
             }
@@ -1046,12 +1101,18 @@ public final class GQL {
 
         public void toString(StringBuffer buffer)
                 throws RepositoryException {
+            int start = buffer.length();
             buffer.append("order by ");
             List<String> names = new ArrayList<String>(Arrays.asList(Text.explode(value, ',')));
             int length = buffer.length();
             String comma = "";
             for (String name : names) {
                 boolean asc;
+                if (name.equals("-")) {
+                    // no order by at all
+                    buffer.delete(start, buffer.length());
+                    return;
+                }
                 if (name.startsWith("-")) {
                     name = name.substring(1);
                     asc = false;
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/query/qom/OperandEvaluator.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/query/qom/OperandEvaluator.java
index eade9ad..8be348d 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/query/qom/OperandEvaluator.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/query/qom/OperandEvaluator.java
@@ -79,7 +79,6 @@ public class OperandEvaluator {
      *
      * @param factory value factory
      * @param variables bind variables
-     * @param locale locale to use in upper- and lower-case conversions
      */
     public OperandEvaluator(
             ValueFactory factory, Map<String, Value> variables) {
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/query/qom/Operator.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/query/qom/Operator.java
index 6258669..59ec087 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/query/qom/Operator.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/query/qom/Operator.java
@@ -1,161 +1,161 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.commons.query.qom;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.query.qom.Comparison;
-import javax.jcr.query.qom.DynamicOperand;
-import javax.jcr.query.qom.QueryObjectModelConstants;
-import javax.jcr.query.qom.QueryObjectModelFactory;
-import javax.jcr.query.qom.StaticOperand;
-
-/**
- * Enumeration of the JCR 2.0 query operators.
- *
- * @since Apache Jackrabbit 2.0
- */
-public enum Operator {
-
-    EQ(QueryObjectModelConstants.JCR_OPERATOR_EQUAL_TO, "="),
-
-    NE(QueryObjectModelConstants.JCR_OPERATOR_NOT_EQUAL_TO, "!=", "<>"),
-
-    GT(QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN, ">"),
-
-    GE(QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN_OR_EQUAL_TO, ">="),
-
-    LT(QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN, "<"),
-
-    LE(QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN_OR_EQUAL_TO, "<="),
-
-    LIKE(QueryObjectModelConstants.JCR_OPERATOR_LIKE, null, "like");
-
-    /**
-     * JCR name of this operator.
-     */
-    private final String name;
-
-    /**
-     * This operator in XPath syntax.
-     */
-    private final String xpath;
-
-    /**
-     * This operator in SQL syntax.
-     */
-    private final String sql;
-
-    private Operator(String name, String op) {
-        this(name, op, op);
-    }
-
-    private Operator(String name, String xpath, String sql) {
-        this.name = name;
-        this.xpath = xpath;
-        this.sql = sql;
-    }
-
-    /**
-     * Returns a comparison between the given operands using this operator.
-     *
-     * @param factory factory for creating the comparison
-     * @param left operand on the left hand side
-     * @param right operand on the right hand side
-     * @return comparison
-     * @throws RepositoryException if the comparison can not be created
-     */
-    public Comparison comparison(
-            QueryObjectModelFactory factory,
-            DynamicOperand left, StaticOperand right)
-            throws RepositoryException {
-        return factory.comparison(left, name, right);
-    }
-
-    /**
-     * Formats an XPath constraint with this operator and the given operands.
-     * The operands are simply used as-is, without any quoting or escaping.
-     *
-     * @param a first operand
-     * @param b second operand
-     * @return XPath constraint, <code>a op b</code> or
-     *         <code>jcr:like(a, b)</code> for {@link #LIKE}
-     */
-    public String formatXpath(String a, String b) {
-        if (this == LIKE) {
-            return "jcr:like(" + a + ", " + b + ")";
-        } else {
-            return a + " " + xpath + " " + b;
-        }
-    }
-
-    /**
-     * Formats an SQL constraint with this operator and the given operands.
-     * The operands are simply used as-is, without any quoting or escaping.
-     *
-     * @param a first operand
-     * @param b second operand
-     * @return SQL constraint, <code>a op b</code>
-     */
-    public String formatSql(String a, String b) {
-        return a + " " + sql + " " + b;
-    }
-
-    /**
-     * Returns the JCR 2.0 name of this query operator.
-     *
-     * @see QueryObjectModelConstants
-     * @return JCR name of this operator
-     */
-    public String toString() {
-        return name;
-    }
-
-    /**
-     * Returns an array of the names of all the JCR 2.0 query operators.
-     *
-     * @return names of all query operators
-     */
-    public static String[] getAllQueryOperators() {
-        return new String[] {
-                EQ.toString(),
-                NE.toString(),
-                GT.toString(),
-                GE.toString(),
-                LT.toString(),
-                LE.toString(),
-                LIKE.toString()
-        };
-    }
-
-    /**
-     * Returns the operator with the given JCR name.
-     *
-     * @param name JCR name of an operator
-     * @return operator with the given name
-     * @throws RepositoryException if the given name is unknown
-     */
-    public static Operator getOperatorByName(String name)
-            throws RepositoryException {
-        for (Operator operator : Operator.values()) {
-            if (operator.name.equals(name)) {
-                return operator;
-            }
-        }
-        throw new RepositoryException("Unknown operator name: " + name);
-    }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.commons.query.qom;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.query.qom.Comparison;
+import javax.jcr.query.qom.DynamicOperand;
+import javax.jcr.query.qom.QueryObjectModelConstants;
+import javax.jcr.query.qom.QueryObjectModelFactory;
+import javax.jcr.query.qom.StaticOperand;
+
+/**
+ * Enumeration of the JCR 2.0 query operators.
+ *
+ * @since Apache Jackrabbit 2.0
+ */
+public enum Operator {
+
+    EQ(QueryObjectModelConstants.JCR_OPERATOR_EQUAL_TO, "="),
+
+    NE(QueryObjectModelConstants.JCR_OPERATOR_NOT_EQUAL_TO, "!=", "<>"),
+
+    GT(QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN, ">"),
+
+    GE(QueryObjectModelConstants.JCR_OPERATOR_GREATER_THAN_OR_EQUAL_TO, ">="),
+
+    LT(QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN, "<"),
+
+    LE(QueryObjectModelConstants.JCR_OPERATOR_LESS_THAN_OR_EQUAL_TO, "<="),
+
+    LIKE(QueryObjectModelConstants.JCR_OPERATOR_LIKE, null, "like");
+
+    /**
+     * JCR name of this operator.
+     */
+    private final String name;
+
+    /**
+     * This operator in XPath syntax.
+     */
+    private final String xpath;
+
+    /**
+     * This operator in SQL syntax.
+     */
+    private final String sql;
+
+    private Operator(String name, String op) {
+        this(name, op, op);
+    }
+
+    private Operator(String name, String xpath, String sql) {
+        this.name = name;
+        this.xpath = xpath;
+        this.sql = sql;
+    }
+
+    /**
+     * Returns a comparison between the given operands using this operator.
+     *
+     * @param factory factory for creating the comparison
+     * @param left operand on the left hand side
+     * @param right operand on the right hand side
+     * @return comparison
+     * @throws RepositoryException if the comparison can not be created
+     */
+    public Comparison comparison(
+            QueryObjectModelFactory factory,
+            DynamicOperand left, StaticOperand right)
+            throws RepositoryException {
+        return factory.comparison(left, name, right);
+    }
+
+    /**
+     * Formats an XPath constraint with this operator and the given operands.
+     * The operands are simply used as-is, without any quoting or escaping.
+     *
+     * @param a first operand
+     * @param b second operand
+     * @return XPath constraint, <code>a op b</code> or
+     *         <code>jcr:like(a, b)</code> for {@link #LIKE}
+     */
+    public String formatXpath(String a, String b) {
+        if (this == LIKE) {
+            return "jcr:like(" + a + ", " + b + ")";
+        } else {
+            return a + " " + xpath + " " + b;
+        }
+    }
+
+    /**
+     * Formats an SQL constraint with this operator and the given operands.
+     * The operands are simply used as-is, without any quoting or escaping.
+     *
+     * @param a first operand
+     * @param b second operand
+     * @return SQL constraint, <code>a op b</code>
+     */
+    public String formatSql(String a, String b) {
+        return a + " " + sql + " " + b;
+    }
+
+    /**
+     * Returns the JCR 2.0 name of this query operator.
+     *
+     * @see QueryObjectModelConstants
+     * @return JCR name of this operator
+     */
+    public String toString() {
+        return name;
+    }
+
+    /**
+     * Returns an array of the names of all the JCR 2.0 query operators.
+     *
+     * @return names of all query operators
+     */
+    public static String[] getAllQueryOperators() {
+        return new String[] {
+                EQ.toString(),
+                NE.toString(),
+                GT.toString(),
+                GE.toString(),
+                LT.toString(),
+                LE.toString(),
+                LIKE.toString()
+        };
+    }
+
+    /**
+     * Returns the operator with the given JCR name.
+     *
+     * @param name JCR name of an operator
+     * @return operator with the given name
+     * @throws RepositoryException if the given name is unknown
+     */
+    public static Operator getOperatorByName(String name)
+            throws RepositoryException {
+        for (Operator operator : Operator.values()) {
+            if (operator.name.equals(name)) {
+                return operator;
+            }
+        }
+        throw new RepositoryException("Unknown operator name: " + name);
+    }
+
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/repository/SingletonRepositoryFactory.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/repository/SingletonRepositoryFactory.java
index fbadde9..e47dbd5 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/repository/SingletonRepositoryFactory.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/repository/SingletonRepositoryFactory.java
@@ -1,49 +1,49 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.commons.repository;
-
-import javax.jcr.Repository;
-
-/**
- * Repository factory that always returns the same configured repository.
- */
-public class SingletonRepositoryFactory implements RepositoryFactory {
-
-    /**
-     * Singleton repository instance.
-     */
-    private final Repository repository;
-
-    /**
-     * Creates a repository factory that always returns the given repository.
-     *
-     * @param repository singleton repository instance.
-     */
-    public SingletonRepositoryFactory(Repository repository) {
-        this.repository = repository;
-    }
-
-    /**
-     * Returns the configured repository instance.
-     *
-     * @return singleton repository instance
-     */
-    public Repository getRepository() {
-        return repository;
-    }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.commons.repository;
+
+import javax.jcr.Repository;
+
+/**
+ * Repository factory that always returns the same configured repository.
+ */
+public class SingletonRepositoryFactory implements RepositoryFactory {
+
+    /**
+     * Singleton repository instance.
+     */
+    private final Repository repository;
+
+    /**
+     * Creates a repository factory that always returns the given repository.
+     *
+     * @param repository singleton repository instance.
+     */
+    public SingletonRepositoryFactory(Repository repository) {
+        this.repository = repository;
+    }
+
+    /**
+     * Returns the configured repository instance.
+     *
+     * @return singleton repository instance
+     */
+    public Repository getRepository() {
+        return repository;
+    }
+
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/visitor/FilteringItemVisitor.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/visitor/FilteringItemVisitor.java
index e2cd44e..cf09d55 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/visitor/FilteringItemVisitor.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/visitor/FilteringItemVisitor.java
@@ -1,237 +1,237 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.commons.visitor;
-
-import java.util.LinkedList;
-
-import javax.jcr.Item;
-import javax.jcr.ItemVisitor;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.RepositoryException;
-
-import org.apache.jackrabbit.commons.predicate.Predicate;
-
-public abstract class FilteringItemVisitor implements ItemVisitor {
-
-    /**
-     * Predicate that defines which items are included.
-     */
-    protected Predicate includePredicate = Predicate.TRUE;
-
-    /**
-     * Predicate that defines which items are traversed.
-     */
-    protected Predicate traversalPredicate = Predicate.TRUE;
-
-    /**
-     * Do we want to walk all properties of nodes?
-     * The default is false.
-     */
-    protected boolean walkProperties = false;
-
-    /**
-     * indicates if traversal should be done in a breadth-first
-     * manner rather than depth-first (which is the default)
-     */
-    protected boolean breadthFirst = false;
-
-    /**
-     * the 0-based level up to which the hierarchy should be traversed
-     * (if it's -1, the hierarchy will be traversed until there are no
-     * more children of the current item)
-     */
-    protected int maxLevel = -1;
-
-    /**
-     * queues used to implement breadth-first traversal
-     */
-    protected LinkedList currentQueue;
-    protected LinkedList nextQueue;
-
-    /**
-     * used to track hierarchy level of item currently being processed
-     */
-    protected int currentLevel;
-
-    public void setMaxLevel(final int ml) {
-        this.maxLevel = ml;
-    }
-
-    public void setBreadthFirst(final boolean flag) {
-        if ( this.breadthFirst != flag ) {
-            this.breadthFirst = flag;
-            if (breadthFirst) {
-                this.currentQueue = new LinkedList();
-                this.nextQueue = new LinkedList();
-            } else {
-                this.currentQueue = null;
-                this.nextQueue = null;
-            }
-
-        }
-    }
-    public void setWalkProperties(final boolean flag) {
-        this.walkProperties = flag;
-    }
-
-    public void setIncludePredicate(final Predicate ip) {
-        this.includePredicate = ip;
-    }
-
-    public void setTraversalPredicate(final Predicate tp) {
-        this.traversalPredicate = tp;
-    }
-
-    /**
-     * Implement this method to add behaviour performed before a
-     * <code>Property</code> is visited.
-     *
-     * @param property the <code>Property</code> that is accepting this visitor.
-     * @param level    hierarchy level of this property (the root node starts at level 0)
-     * @throws RepositoryException if an error occurrs
-     */
-    protected abstract void entering(Property property, int level)
-        throws RepositoryException;
-
-    /**
-     * Implement this method to add behaviour performed before a
-     * <code>Node</code> is visited.
-     *
-     * @param node  the <code>Node</code> that is accepting this visitor.
-     * @param level hierarchy level of this node (the root node starts at level 0)
-     * @throws RepositoryException if an error occurrs
-     */
-    protected abstract void entering(Node node, int level)
-        throws RepositoryException;
-
-    /**
-     * Implement this method to add behaviour performed after a
-     * <code>Property</code> is visited.
-     *
-     * @param property the <code>Property</code> that is accepting this visitor.
-     * @param level    hierarchy level of this property (the root node starts at level 0)
-     * @throws RepositoryException if an error occurrs
-     */
-    protected abstract void leaving(Property property, int level)
-        throws RepositoryException;
-
-    /**
-     * Implement this method to add behaviour performed after a
-     * <code>Node</code> is visited.
-     *
-     * @param node  the <code>Node</code> that is accepting this visitor.
-     * @param level hierarchy level of this node (the root node starts at level 0)
-     * @throws RepositoryException if an error occurrs
-     */
-    protected abstract void leaving(Node node, int level)
-        throws RepositoryException;
-
-    /**
-     * Called when the Visitor is passed to a <code>Property</code>.
-     * <p/>
-     * It calls <code>TraversingItemVisitor.entering(Property, int)</code> followed by
-     * <code>TraversingItemVisitor.leaving(Property, int)</code>. Implement these abstract methods to
-     * specify behaviour on 'arrival at' and 'after leaving' the <code>Property</code>.
-     * <p/>
-     * <p/>
-     * If this method throws, the visiting process is aborted.
-     *
-     * @param property the <code>Property</code> that is accepting this visitor.
-     * @throws RepositoryException if an error occurrs
-     */
-    public void visit(Property property) throws RepositoryException {
-        if ( this.walkProperties && this.includePredicate.evaluate(property) ) {
-            entering(property, currentLevel);
-            leaving(property, currentLevel);
-        }
-    }
-
-    /**
-     * Called when the Visitor is passed to a <code>Node</code>.
-     * <p/>
-     * It calls <code>TraversingItemVisitor.entering(Node, int)</code> followed by
-     * <code>TraversingItemVisitor.leaving(Node, int)</code>. Implement these abstract methods to
-     * specify behaviour on 'arrival at' and 'after leaving' the <code>Node</code>.
-     * <p/>
-     * If this method throws, the visiting process is aborted.
-     *
-     * @param node the <code>Node</code> that is accepting this visitor.
-     * @throws RepositoryException if an error occurrs
-     */
-    public void visit(Node node)
-    throws RepositoryException {
-        if ( this.traversalPredicate.evaluate(node) ) {
-            if ( this.includePredicate == this.traversalPredicate || this.includePredicate.evaluate(node) )  {
-                try {
-                    if (!breadthFirst) {
-                        // depth-first traversal
-                        entering(node, currentLevel);
-                        if (maxLevel == -1 || currentLevel < maxLevel) {
-                            currentLevel++;
-                            if ( this.walkProperties ) {
-                                PropertyIterator propIter = node.getProperties();
-                                while (propIter.hasNext()) {
-                                    propIter.nextProperty().accept(this);
-                                }
-                            }
-                            NodeIterator nodeIter = node.getNodes();
-                            while (nodeIter.hasNext()) {
-                                nodeIter.nextNode().accept(this);
-                            }
-                            currentLevel--;
-                        }
-                        leaving(node, currentLevel);
-                    } else {
-                        // breadth-first traversal
-                        entering(node, currentLevel);
-                        leaving(node, currentLevel);
-
-                        if (maxLevel == -1 || currentLevel < maxLevel) {
-                            if ( this.walkProperties ) {
-                                PropertyIterator propIter = node.getProperties();
-                                while (propIter.hasNext()) {
-                                    nextQueue.addLast(propIter.nextProperty());
-                                }
-                            }
-                            NodeIterator nodeIter = node.getNodes();
-                            while (nodeIter.hasNext()) {
-                                nextQueue.addLast(nodeIter.nextNode());
-                            }
-                        }
-
-                        while (!currentQueue.isEmpty() || !nextQueue.isEmpty()) {
-                            if (currentQueue.isEmpty()) {
-                                currentLevel++;
-                                currentQueue = nextQueue;
-                                nextQueue = new LinkedList();
-                            }
-                            Item e = (Item) currentQueue.removeFirst();
-                            e.accept(this);
-                        }
-                        currentLevel = 0;
-                    }
-                } catch (RepositoryException re) {
-                    currentLevel = 0;
-                    throw re;
-                }
-            }
-        }
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.commons.visitor;
+
+import java.util.LinkedList;
+
+import javax.jcr.Item;
+import javax.jcr.ItemVisitor;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.commons.predicate.Predicate;
+
+public abstract class FilteringItemVisitor implements ItemVisitor {
+
+    /**
+     * Predicate that defines which items are included.
+     */
+    protected Predicate includePredicate = Predicate.TRUE;
+
+    /**
+     * Predicate that defines which items are traversed.
+     */
+    protected Predicate traversalPredicate = Predicate.TRUE;
+
+    /**
+     * Do we want to walk all properties of nodes?
+     * The default is false.
+     */
+    protected boolean walkProperties = false;
+
+    /**
+     * indicates if traversal should be done in a breadth-first
+     * manner rather than depth-first (which is the default)
+     */
+    protected boolean breadthFirst = false;
+
+    /**
+     * the 0-based level up to which the hierarchy should be traversed
+     * (if it's -1, the hierarchy will be traversed until there are no
+     * more children of the current item)
+     */
+    protected int maxLevel = -1;
+
+    /**
+     * queues used to implement breadth-first traversal
+     */
+    protected LinkedList currentQueue;
+    protected LinkedList nextQueue;
+
+    /**
+     * used to track hierarchy level of item currently being processed
+     */
+    protected int currentLevel;
+
+    public void setMaxLevel(final int ml) {
+        this.maxLevel = ml;
+    }
+
+    public void setBreadthFirst(final boolean flag) {
+        if ( this.breadthFirst != flag ) {
+            this.breadthFirst = flag;
+            if (breadthFirst) {
+                this.currentQueue = new LinkedList();
+                this.nextQueue = new LinkedList();
+            } else {
+                this.currentQueue = null;
+                this.nextQueue = null;
+            }
+
+        }
+    }
+    public void setWalkProperties(final boolean flag) {
+        this.walkProperties = flag;
+    }
+
+    public void setIncludePredicate(final Predicate ip) {
+        this.includePredicate = ip;
+    }
+
+    public void setTraversalPredicate(final Predicate tp) {
+        this.traversalPredicate = tp;
+    }
+
+    /**
+     * Implement this method to add behaviour performed before a
+     * <code>Property</code> is visited.
+     *
+     * @param property the <code>Property</code> that is accepting this visitor.
+     * @param level    hierarchy level of this property (the root node starts at level 0)
+     * @throws RepositoryException if an error occurrs
+     */
+    protected abstract void entering(Property property, int level)
+        throws RepositoryException;
+
+    /**
+     * Implement this method to add behaviour performed before a
+     * <code>Node</code> is visited.
+     *
+     * @param node  the <code>Node</code> that is accepting this visitor.
+     * @param level hierarchy level of this node (the root node starts at level 0)
+     * @throws RepositoryException if an error occurrs
+     */
+    protected abstract void entering(Node node, int level)
+        throws RepositoryException;
+
+    /**
+     * Implement this method to add behaviour performed after a
+     * <code>Property</code> is visited.
+     *
+     * @param property the <code>Property</code> that is accepting this visitor.
+     * @param level    hierarchy level of this property (the root node starts at level 0)
+     * @throws RepositoryException if an error occurrs
+     */
+    protected abstract void leaving(Property property, int level)
+        throws RepositoryException;
+
+    /**
+     * Implement this method to add behaviour performed after a
+     * <code>Node</code> is visited.
+     *
+     * @param node  the <code>Node</code> that is accepting this visitor.
+     * @param level hierarchy level of this node (the root node starts at level 0)
+     * @throws RepositoryException if an error occurrs
+     */
+    protected abstract void leaving(Node node, int level)
+        throws RepositoryException;
+
+    /**
+     * Called when the Visitor is passed to a <code>Property</code>.
+     * <p>
+     * It calls <code>TraversingItemVisitor.entering(Property, int)</code> followed by
+     * <code>TraversingItemVisitor.leaving(Property, int)</code>. Implement these abstract methods to
+     * specify behaviour on 'arrival at' and 'after leaving' the <code>Property</code>.
+     * <p>
+     * <p>
+     * If this method throws, the visiting process is aborted.
+     *
+     * @param property the <code>Property</code> that is accepting this visitor.
+     * @throws RepositoryException if an error occurrs
+     */
+    public void visit(Property property) throws RepositoryException {
+        if ( this.walkProperties && this.includePredicate.evaluate(property) ) {
+            entering(property, currentLevel);
+            leaving(property, currentLevel);
+        }
+    }
+
+    /**
+     * Called when the Visitor is passed to a <code>Node</code>.
+     * <p>
+     * It calls <code>TraversingItemVisitor.entering(Node, int)</code> followed by
+     * <code>TraversingItemVisitor.leaving(Node, int)</code>. Implement these abstract methods to
+     * specify behaviour on 'arrival at' and 'after leaving' the <code>Node</code>.
+     * <p>
+     * If this method throws, the visiting process is aborted.
+     *
+     * @param node the <code>Node</code> that is accepting this visitor.
+     * @throws RepositoryException if an error occurrs
+     */
+    public void visit(Node node)
+    throws RepositoryException {
+        if ( this.traversalPredicate.evaluate(node) ) {
+            if ( this.includePredicate == this.traversalPredicate || this.includePredicate.evaluate(node) )  {
+                try {
+                    if (!breadthFirst) {
+                        // depth-first traversal
+                        entering(node, currentLevel);
+                        if (maxLevel == -1 || currentLevel < maxLevel) {
+                            currentLevel++;
+                            if ( this.walkProperties ) {
+                                PropertyIterator propIter = node.getProperties();
+                                while (propIter.hasNext()) {
+                                    propIter.nextProperty().accept(this);
+                                }
+                            }
+                            NodeIterator nodeIter = node.getNodes();
+                            while (nodeIter.hasNext()) {
+                                nodeIter.nextNode().accept(this);
+                            }
+                            currentLevel--;
+                        }
+                        leaving(node, currentLevel);
+                    } else {
+                        // breadth-first traversal
+                        entering(node, currentLevel);
+                        leaving(node, currentLevel);
+
+                        if (maxLevel == -1 || currentLevel < maxLevel) {
+                            if ( this.walkProperties ) {
+                                PropertyIterator propIter = node.getProperties();
+                                while (propIter.hasNext()) {
+                                    nextQueue.addLast(propIter.nextProperty());
+                                }
+                            }
+                            NodeIterator nodeIter = node.getNodes();
+                            while (nodeIter.hasNext()) {
+                                nextQueue.addLast(nodeIter.nextNode());
+                            }
+                        }
+
+                        while (!currentQueue.isEmpty() || !nextQueue.isEmpty()) {
+                            if (currentQueue.isEmpty()) {
+                                currentLevel++;
+                                currentQueue = nextQueue;
+                                nextQueue = new LinkedList();
+                            }
+                            Item e = (Item) currentQueue.removeFirst();
+                            e.accept(this);
+                        }
+                        currentLevel = 0;
+                    }
+                } catch (RepositoryException re) {
+                    currentLevel = 0;
+                    throw re;
+                }
+            }
+        }
+    }
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/webdav/AtomFeedConstants.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/webdav/AtomFeedConstants.java
index d0babd9..c642504 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/webdav/AtomFeedConstants.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/webdav/AtomFeedConstants.java
@@ -1,53 +1,53 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.commons.webdav;
-
-import javax.xml.namespace.QName;
-
-/**
- * <code>AtomFeedConstants</code> provides string constants for Atom feed 
- * (RFC 4287) resources.
- */
-public interface AtomFeedConstants {
-
-    /**
-     * Namespace URI for RFC 4287 elements.
-     */
-    public static final String NS_URI = "http://www.w3.org/2005/Atom";
-    
-    public static final String MEDIATYPE = "application/atom+xml";
-
-    public static final String XML_AUTHOR = "author";
-    public static final String XML_CONTENT = "content";
-    public static final String XML_ENTRY = "entry";
-    public static final String XML_FEED = "feed";
-    public static final String XML_ID = "id";
-    public static final String XML_LINK = "link";
-    public static final String XML_NAME = "name";
-    public static final String XML_TITLE = "title";
-    public static final String XML_UPDATED = "updated";
-
-    public static final QName N_AUTHOR = new QName(NS_URI, XML_AUTHOR);
-    public static final QName N_CONTENT = new QName(NS_URI, XML_CONTENT);
-    public static final QName N_ENTRY = new QName(NS_URI, XML_ENTRY);
-    public static final QName N_FEED = new QName(NS_URI, XML_FEED);
-    public static final QName N_ID = new QName(NS_URI, XML_ID);
-    public static final QName N_LINK = new QName(NS_URI, XML_LINK);
-    public static final QName N_NAME = new QName(NS_URI, XML_NAME);
-    public static final QName N_TITLE = new QName(NS_URI, XML_TITLE);
-    public static final QName N_UPDATED = new QName(NS_URI, XML_UPDATED);
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.commons.webdav;
+
+import javax.xml.namespace.QName;
+
+/**
+ * <code>AtomFeedConstants</code> provides string constants for Atom feed 
+ * (RFC 4287) resources.
+ */
+public interface AtomFeedConstants {
+
+    /**
+     * Namespace URI for RFC 4287 elements.
+     */
+    public static final String NS_URI = "http://www.w3.org/2005/Atom";
+    
+    public static final String MEDIATYPE = "application/atom+xml";
+
+    public static final String XML_AUTHOR = "author";
+    public static final String XML_CONTENT = "content";
+    public static final String XML_ENTRY = "entry";
+    public static final String XML_FEED = "feed";
+    public static final String XML_ID = "id";
+    public static final String XML_LINK = "link";
+    public static final String XML_NAME = "name";
+    public static final String XML_TITLE = "title";
+    public static final String XML_UPDATED = "updated";
+
+    public static final QName N_AUTHOR = new QName(NS_URI, XML_AUTHOR);
+    public static final QName N_CONTENT = new QName(NS_URI, XML_CONTENT);
+    public static final QName N_ENTRY = new QName(NS_URI, XML_ENTRY);
+    public static final QName N_FEED = new QName(NS_URI, XML_FEED);
+    public static final QName N_ID = new QName(NS_URI, XML_ID);
+    public static final QName N_LINK = new QName(NS_URI, XML_LINK);
+    public static final QName N_NAME = new QName(NS_URI, XML_NAME);
+    public static final QName N_TITLE = new QName(NS_URI, XML_TITLE);
+    public static final QName N_UPDATED = new QName(NS_URI, XML_UPDATED);
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/webdav/package-info.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/webdav/package-info.java
index a2df4e8..f30789e 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/webdav/package-info.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/webdav/package-info.java
@@ -14,5 +14,5 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- at aQute.bnd.annotation.Version("2.4.0")
+ at aQute.bnd.annotation.Version("2.5.0")
 package org.apache.jackrabbit.commons.webdav;
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/SerializingContentHandler.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/SerializingContentHandler.java
index 09ee07d..6222161 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/SerializingContentHandler.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/SerializingContentHandler.java
@@ -116,7 +116,7 @@ public class SerializingContentHandler extends DefaultContentHandler {
     /**
      * Creates a serializing content handler that writes to the given stream.
      *
-     * @param stream serialization target
+     * @param output serialization target
      * @return serializing content handler
      * @throws SAXException if the content handler could not be initialized
      */
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/ToXmlContentHandler.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/ToXmlContentHandler.java
index e6510c8..8b0129b 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/ToXmlContentHandler.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/xml/ToXmlContentHandler.java
@@ -274,7 +274,7 @@ public class ToXmlContentHandler extends DefaultHandler {
      * Returns the serialized XML document (assuming the default no-argument
      * constructor was used).
      *
-     * @param serialized XML document
+     * @return serialized XML document
      */
     public String toString() {
         return writer.toString();
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/QueryStatCore.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/QueryStatCore.java
new file mode 100644
index 0000000..7d12120
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/QueryStatCore.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.stats;
+
+import org.apache.jackrabbit.api.stats.QueryStat;
+
+/**
+ * Extends external facing {@link QueryStat} with some internal operations
+ * 
+ */
+public interface QueryStatCore extends QueryStat {
+
+    /**
+     * Logs the call of each query ran on the repository.
+     * 
+     * @param language
+     *            the query language, see
+     *            {@link org.apache.jackrabbit.spi.commons.name.NameConstants#JCR_LANGUAGE}
+     * @param statement
+     *            the query
+     * @param durationMs
+     *            time in ms
+     */
+    void logQuery(final String language, final String statement, long durationMs);
+
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/QueryStatDtoComparator.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/QueryStatDtoComparator.java
new file mode 100644
index 0000000..7a14e93
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/QueryStatDtoComparator.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.stats;
+
+import java.util.Comparator;
+
+import org.apache.jackrabbit.api.stats.QueryStatDto;
+
+/**
+ * QueryStatDto comparator by duration
+ * 
+ */
+public class QueryStatDtoComparator implements Comparator<QueryStatDto> {
+    public int compare(QueryStatDto o1, QueryStatDto o2) {
+        return new Long(o1.getDuration()).compareTo(o2.getDuration());
+    }
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/QueryStatDtoImpl.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/QueryStatDtoImpl.java
new file mode 100644
index 0000000..06323aa
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/QueryStatDtoImpl.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.stats;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import org.apache.jackrabbit.api.stats.QueryStatDto;
+
+/**
+ * Object that holds statistical info about a query.
+ * 
+ */
+public class QueryStatDtoImpl implements QueryStatDto {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * lazy, computed at call time
+     */
+    private long position;
+
+    /**
+     * the time that the query was created
+     */
+    private final Date creationTime;
+
+    /**
+     * run duration in ms
+     */
+    private final long durationMs;
+
+    /**
+     * query language
+     */
+    private final String language;
+
+    /**
+     * query statement
+     */
+    private final String statement;
+
+    /**
+     * used in popular queries list
+     */
+    private int occurrenceCount = 1;
+
+    public QueryStatDtoImpl(final String language, final String statement,
+            long durationMs) {
+        this.durationMs = durationMs;
+        this.language = language;
+        this.statement = statement;
+
+        Calendar c = Calendar.getInstance();
+        c.setTimeInMillis(System.currentTimeMillis() - durationMs);
+        this.creationTime = c.getTime();
+    }
+
+    public long getDuration() {
+        return durationMs;
+    }
+
+    public String getLanguage() {
+        return language;
+    }
+
+    public String getStatement() {
+        return statement;
+    }
+
+    public String getCreationTime() {
+        return creationTime.toString();
+    }
+
+    public long getPosition() {
+        return position;
+    }
+
+    public void setPosition(long position) {
+        this.position = position;
+    }
+
+    @Override
+    public String toString() {
+        return "QueryStat [creationTime=" + creationTime + ", duration="
+                + durationMs + ", position " + position + ", language="
+                + language + ", statement=" + statement + "]";
+    }
+
+    public int getOccurrenceCount() {
+        return occurrenceCount;
+    }
+
+    public void setOccurrenceCount(int occurrenceCount) {
+        this.occurrenceCount = occurrenceCount;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((language == null) ? 0 : language.hashCode());
+        result = prime * result
+                + ((statement == null) ? 0 : statement.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        QueryStatDtoImpl other = (QueryStatDtoImpl) obj;
+        if (language == null) {
+            if (other.language != null)
+                return false;
+        } else if (!language.equals(other.language))
+            return false;
+        if (statement == null) {
+            if (other.statement != null)
+                return false;
+        } else if (!statement.equals(other.statement))
+            return false;
+        return true;
+    }
+
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/QueryStatDtoOccurrenceComparator.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/QueryStatDtoOccurrenceComparator.java
new file mode 100644
index 0000000..dcaf5a6
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/QueryStatDtoOccurrenceComparator.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.stats;
+
+import java.util.Comparator;
+
+/**
+ * QueryStatDto comparator by occurrence count
+ * 
+ * used by the popular queries queue
+ * 
+ */
+public class QueryStatDtoOccurrenceComparator implements
+        Comparator<QueryStatDtoImpl> {
+    public int compare(QueryStatDtoImpl o1, QueryStatDtoImpl o2) {
+        return new Integer(o1.getOccurrenceCount()).compareTo(o2
+                .getOccurrenceCount());
+    }
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/QueryStatImpl.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/QueryStatImpl.java
new file mode 100644
index 0000000..fb23500
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/QueryStatImpl.java
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.stats;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.concurrent.PriorityBlockingQueue;
+
+import org.apache.jackrabbit.api.stats.QueryStatDto;
+
+/**
+ * Default {@link QueryStatCore} implementation
+ * 
+ */
+public class QueryStatImpl implements QueryStatCore {
+
+    private final static Comparator<QueryStatDto> comparator = new QueryStatDtoComparator();
+
+    private final BoundedPriorityBlockingQueue<QueryStatDto> slowQueries = new BoundedPriorityBlockingQueue<QueryStatDto>(
+            15, comparator);
+
+    private final static Comparator<QueryStatDtoImpl> comparatorOccurrence = new QueryStatDtoOccurrenceComparator();
+
+    /**
+     * the real queue size will be bigger than the desired number of popular
+     * queries by POPULAR_QUEUE_MULTIPLIER times
+     */
+    private static final int POPULAR_QUEUE_MULTIPLIER = 5;
+
+    private final BoundedPriorityBlockingQueue<QueryStatDtoImpl> popularQueries = new BoundedPriorityBlockingQueue<QueryStatDtoImpl>(
+            15 * POPULAR_QUEUE_MULTIPLIER, comparatorOccurrence);
+
+    private static final class BoundedPriorityBlockingQueue<E> extends
+            PriorityBlockingQueue<E> {
+
+        private static final long serialVersionUID = 1L;
+        private int maxSize;
+
+        public BoundedPriorityBlockingQueue(int maxSize,
+                Comparator<? super E> comparator) {
+            super(maxSize + 1, comparator);
+            this.maxSize = maxSize;
+        }
+
+        @Override
+        public boolean offer(E e) {
+            boolean s = super.offer(e);
+            if (!s) {
+                return false;
+            }
+            if (size() > maxSize) {
+                poll();
+            }
+            return true;
+        }
+
+        public synchronized void setMaxSize(int maxSize) {
+            if (maxSize < this.maxSize) {
+                // shrink the queue
+                int delta = super.size() - maxSize;
+                for (int i = 0; i < delta; i++) {
+                    E t = poll();
+                    if (t == null) {
+                        break;
+                    }
+                }
+            }
+            this.maxSize = maxSize;
+        }
+
+        public int getMaxSize() {
+            return maxSize;
+        }
+    }
+
+    private boolean enabled = false;
+
+    public QueryStatImpl() {
+    }
+
+    public int getSlowQueriesQueueSize() {
+        return slowQueries.getMaxSize();
+    }
+
+    public void setSlowQueriesQueueSize(int size) {
+        slowQueries.setMaxSize(size);
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public synchronized void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public void logQuery(final String language, final String statement,
+            long durationMs) {
+        if (!enabled) {
+            return;
+        }
+        final QueryStatDtoImpl qs = new QueryStatDtoImpl(language, statement,
+                durationMs);
+        slowQueries.offer(qs);
+
+        synchronized (popularQueries) {
+            Iterator<QueryStatDtoImpl> iterator = popularQueries.iterator();
+            while (iterator.hasNext()) {
+                QueryStatDtoImpl qsdi = iterator.next();
+                if (qsdi.equals(qs)) {
+                    qs.setOccurrenceCount(qsdi.getOccurrenceCount() + 1);
+                    iterator.remove();
+                    break;
+                }
+            }
+            popularQueries.offer(qs);
+        }
+    }
+
+    public void clearSlowQueriesQueue() {
+        slowQueries.clear();
+    }
+
+    public QueryStatDto[] getSlowQueries() {
+        QueryStatDto[] top = slowQueries.toArray(new QueryStatDto[slowQueries
+                .size()]);
+        Arrays.sort(top, Collections.reverseOrder(comparator));
+        for (int i = 0; i < top.length; i++) {
+            top[i].setPosition(i + 1);
+        }
+        return top;
+    }
+
+    public QueryStatDto[] getPopularQueries() {
+        QueryStatDtoImpl[] top;
+        int size = 0;
+        int maxSize = 0;
+        synchronized (popularQueries) {
+            top = popularQueries.toArray(new QueryStatDtoImpl[popularQueries
+                    .size()]);
+            size = popularQueries.size();
+            maxSize = popularQueries.getMaxSize();
+        }
+        Arrays.sort(top, Collections.reverseOrder(comparatorOccurrence));
+        int retSize = Math.min(size, maxSize / POPULAR_QUEUE_MULTIPLIER);
+        QueryStatDto[] retval = new QueryStatDto[retSize];
+        for (int i = 0; i < retSize; i++) {
+            retval[i] = top[i];
+            retval[i].setPosition(i + 1);
+        }
+        return retval;
+    }
+
+    public int getPopularQueriesQueueSize() {
+        return popularQueries.getMaxSize() / POPULAR_QUEUE_MULTIPLIER;
+    }
+
+    public void setPopularQueriesQueueSize(int size) {
+        popularQueries.setMaxSize(size * POPULAR_QUEUE_MULTIPLIER);
+    }
+
+    public void clearPopularQueriesQueue() {
+        popularQueries.clear();
+    }
+
+    public void reset() {
+        clearSlowQueriesQueue();
+        clearPopularQueriesQueue();
+    }
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/RepositoryStatisticsImpl.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/RepositoryStatisticsImpl.java
new file mode 100644
index 0000000..6819579
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/RepositoryStatisticsImpl.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.stats;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.jackrabbit.api.stats.RepositoryStatistics;
+import org.apache.jackrabbit.api.stats.TimeSeries;
+
+public class RepositoryStatisticsImpl implements
+        Iterable<Map.Entry<String, TimeSeries>>, RepositoryStatistics {
+
+    private final Map<String, TimeSeriesRecorder> recorders =
+            new HashMap<String, TimeSeriesRecorder>();
+
+    private final Map<String, TimeSeriesAverage> avg =
+            new HashMap<String, TimeSeriesAverage>();
+
+    public RepositoryStatisticsImpl() {
+        getOrCreateRecorder(Type.SESSION_COUNT);
+        getOrCreateRecorder(Type.SESSION_LOGIN_COUNTER);
+
+        createAvg(Type.SESSION_READ_COUNTER, Type.SESSION_READ_DURATION,
+                Type.SESSION_READ_AVERAGE);
+        createAvg(Type.SESSION_WRITE_COUNTER, Type.SESSION_WRITE_DURATION,
+                Type.SESSION_WRITE_AVERAGE);
+        createAvg(Type.BUNDLE_CACHE_MISS_COUNTER,
+                Type.BUNDLE_CACHE_MISS_DURATION, Type.BUNDLE_CACHE_MISS_AVERAGE);
+        createAvg(Type.BUNDLE_WRITE_COUNTER, Type.BUNDLE_WRITE_DURATION,
+                Type.BUNDLE_WRITE_AVERAGE);
+        createAvg(Type.QUERY_COUNT, Type.QUERY_DURATION,
+                Type.QUERY_AVERAGE);
+        createAvg(Type.OBSERVATION_EVENT_COUNTER, Type.OBSERVATION_EVENT_DURATION,
+                Type.OBSERVATION_EVENT_AVERAGE);
+    }
+
+    private void createAvg(Type count, Type duration, Type avgTs) {
+        avg.put(avgTs.name(), new TimeSeriesAverage(getOrCreateRecorder(duration),
+                getOrCreateRecorder(count)));
+    }
+
+    public RepositoryStatisticsImpl(ScheduledExecutorService executor) {
+        this();
+        executor.scheduleAtFixedRate(new Runnable() {
+            public void run() {
+                recordOneSecond();
+            }
+        }, 1, 1, TimeUnit.SECONDS);
+    }
+
+    public synchronized Iterator<Entry<String, TimeSeries>> iterator() {
+        Map<String, TimeSeries> map = new TreeMap<String, TimeSeries>();
+        map.putAll(recorders);
+        map.putAll(avg);
+        return map.entrySet().iterator();
+    }
+
+    public AtomicLong getCounter(Type type) {
+        return getOrCreateRecorder(type).getCounter();
+    }
+
+    public AtomicLong getCounter(String type, boolean resetValueEachSecond) {
+        return getOrCreateRecorder(type, resetValueEachSecond).getCounter();
+    }
+
+    public TimeSeries getTimeSeries(Type type) {
+        return getTimeSeries(type.name(), type.isResetValueEachSecond());
+    }
+
+    public TimeSeries getTimeSeries(String type, boolean resetValueEachSecond) {
+        if (avg.containsKey(type)) {
+            return avg.get(type);
+        }
+        return getOrCreateRecorder(type, resetValueEachSecond);
+    }
+
+    private synchronized TimeSeriesRecorder getOrCreateRecorder(Type type) {
+        return getOrCreateRecorder(type.name(), type.isResetValueEachSecond());
+    }
+
+    private synchronized TimeSeriesRecorder getOrCreateRecorder(String type, boolean resetValueEachSecond) {
+        TimeSeriesRecorder recorder = recorders.get(type);
+        if (recorder == null) {
+            recorder = new TimeSeriesRecorder(resetValueEachSecond);
+            recorders.put(type, recorder);
+        }
+        return recorder;
+    }
+
+    private synchronized void recordOneSecond() {
+        for (TimeSeriesRecorder recorder : recorders.values()) {
+            recorder.recordOneSecond();
+        }
+    }
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/TimeSeriesAverage.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/TimeSeriesAverage.java
new file mode 100644
index 0000000..457e5a6
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/TimeSeriesAverage.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.stats;
+
+import org.apache.jackrabbit.api.stats.TimeSeries;
+
+/**
+ * Time series of the average calculated by dividing a measured
+ * value by the counter of events during the measurement period.
+ */
+public class TimeSeriesAverage implements TimeSeries {
+
+    /** Value */
+    private final TimeSeries value;
+
+    /** Value */
+    private final TimeSeries counter;
+
+    public TimeSeriesAverage(TimeSeries value, TimeSeries counter) {
+        this.value = value;
+        this.counter = counter;
+    }
+
+    //----------------------------------------------------------< TimeSeries >
+
+    public long[] getValuePerSecond() {
+        long[] values = value.getValuePerSecond();
+        long[] counts = counter.getValuePerSecond();
+        return divide(values, counts);
+    }
+
+    public long[] getValuePerMinute() {
+        long[] values = value.getValuePerMinute();
+        long[] counts = counter.getValuePerMinute();
+        return divide(values, counts);
+    }
+
+    public synchronized long[] getValuePerHour() {
+        long[] values = value.getValuePerHour();
+        long[] counts = counter.getValuePerHour();
+        return divide(values, counts);
+    }
+
+    public synchronized long[] getValuePerWeek() {
+        long[] values = value.getValuePerWeek();
+        long[] counts = counter.getValuePerWeek();
+        return divide(values, counts);
+    }
+
+    //-------------------------------------------------------------< private >
+
+    /**
+     * Per-entry division of two arrays.
+     *
+     * @param a array
+     * @param b array
+     * @return result of division
+     */
+    private long[] divide(long[] a, long[] b) {
+        long[] c = new long[a.length];
+        for (int i = 0; i < a.length; i++) {
+            if (b[i] != 0) {
+                c[i] = a[i] / b[i];
+            } else {
+                c[i] = 0;
+            }
+        }
+        return c;
+    }
+
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/TimeSeriesMax.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/TimeSeriesMax.java
new file mode 100644
index 0000000..82bef6d
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/TimeSeriesMax.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.jackrabbit.stats;
+
+import org.apache.jackrabbit.api.stats.TimeSeries;
+
+/**
+ * Time series of the maximum value recorded in a period
+ */
+public class TimeSeriesMax implements TimeSeries {
+    private final MaxValue max = new MaxValue(0);
+    private final long[] perSecond = new long[60];
+    private final long[] perMinute = new long[60];
+    private final long[] perHour = new long[7 * 24];
+    private final long[] perWeek = new long[3 * 52];
+
+    /** Current second (index in {@link #perSecond}) */
+    private int seconds;
+
+    /** Current minute (index in {@link #perMinute}) */
+    private int minutes;
+
+    /** Current hour (index in {@link #perHour}) */
+    private int hours;
+
+    /** Current week (index in {@link #perWeek}) */
+    private int weeks;
+
+    public void recordValue(long value) {
+        max.setIfMaximal(value);
+    }
+
+    /**
+     * Records the number of measured values over the past second and resets
+     * the counter. This method should be scheduled to be called once per
+     * second.
+     */
+    public synchronized void recordOneSecond() {
+        perSecond[seconds++] = max.getAndSetValue(0);
+        if (seconds == perSecond.length) {
+            seconds = 0;
+            perMinute[minutes++] = max(perSecond);
+        }
+        if (minutes == perMinute.length) {
+            minutes = 0;
+            perHour[hours++] = max(perMinute);
+        }
+        if (hours == perHour.length) {
+            hours = 0;
+            perWeek[weeks++] = max(perHour);
+        }
+        if (weeks == perWeek.length) {
+            weeks = 0;
+        }
+    }
+
+    @Override
+    public synchronized long[] getValuePerSecond() {
+        return cyclicCopyFrom(perSecond, seconds);
+    }
+
+    @Override
+    public synchronized long[] getValuePerMinute() {
+        return cyclicCopyFrom(perMinute, minutes);
+    }
+
+    @Override
+    public synchronized long[] getValuePerHour() {
+        return cyclicCopyFrom(perHour, hours);
+    }
+
+    @Override
+    public synchronized long[] getValuePerWeek() {
+        return cyclicCopyFrom(perWeek, weeks);
+    }
+
+    /**
+     * Returns the maximum of all entries in the given array.
+     */
+    private static long max(long[] array) {
+        long max = Long.MIN_VALUE;
+        for (long v : array) {
+            if (v > max) {
+                max = v;
+            }
+        }
+        return max;
+    }
+
+    /**
+     * Returns a copy of the given cyclical array, with the element at
+     * the given position as the first element of the returned array.
+     *
+     * @param array cyclical array
+     * @param pos position of the first element
+     * @return copy of the array
+     */
+    private static long[] cyclicCopyFrom(long[] array, int pos) {
+        long[] reverse = new long[array.length];
+        for (int i = 0; i < array.length; i++) {
+            reverse[i] = array[(pos + i) % array.length];
+        }
+        return reverse;
+    }
+
+    private static class MaxValue {
+        private long value;
+
+        public MaxValue(long value) {
+            this.value = value;
+        }
+
+        public synchronized long getAndSetValue(long value) {
+            long v = this.value;
+            this.value = value;
+            return v;
+        }
+
+        public synchronized void setIfMaximal(long value) {
+            if (value > this.value) {
+                this.value = value;
+            }
+        }
+    }
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/TimeSeriesRecorder.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/TimeSeriesRecorder.java
new file mode 100755
index 0000000..310c636
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/TimeSeriesRecorder.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.stats;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.jackrabbit.api.stats.TimeSeries;
+import org.apache.jackrabbit.api.stats.RepositoryStatistics.Type;
+
+/**
+ * Recorder of a time series. An instance of this class records (and clears)
+ * the state of a given {@link AtomicLong} counter once every second and
+ * exposes the collected time series through the {@link TimeSeries}
+ * interface.
+ */
+public class TimeSeriesRecorder implements TimeSeries {
+
+    /** Value */
+    private final AtomicLong counter = new AtomicLong();
+
+    /** Whether to reset value each second */
+    private final boolean resetValueEachSecond;
+
+    /** Measured value per second over the last minute. */
+    private final long[] valuePerSecond = new long[60];
+
+    /** Measured value per minute over the last hour. */
+    private final long[] valuePerMinute = new long[60];
+
+    /** Measured value per hour over the last week. */
+    private final long[] valuePerHour = new long[7 * 24];
+
+    /** Measured value per week over the last three years. */
+    private final long[] valuePerWeek = new long[3 * 52];
+
+    /** Current second (index in {@link #valuePerSecond}) */
+    private int seconds = 0;
+
+    /** Current minute (index in {@link #valuePerMinute}) */
+    private int minutes = 0;
+
+    /** Current hour (index in {@link #valuePerHour}) */
+    private int hours = 0;
+
+    /** Current week (index in {@link #valuePerWeek}) */
+    private int weeks = 0;
+
+    public TimeSeriesRecorder(Type type) {
+        this(type.isResetValueEachSecond());
+    }
+
+    public TimeSeriesRecorder(boolean resetValueEachSecond) {
+        this.resetValueEachSecond = resetValueEachSecond;
+    }
+
+    /**
+     * Returns the {@link AtomicLong} instance used to measure the value for
+     * the time series.
+     *
+     * @return value
+     */
+    public AtomicLong getCounter() {
+        return counter;
+    }
+
+    /**
+     * Records the number of measured values over the past second and resets
+     * the counter. This method should be scheduled to be called once per
+     * second.
+     */
+    public synchronized void recordOneSecond() {
+        if (resetValueEachSecond) {
+            valuePerSecond[seconds++] = counter.getAndSet(0);
+        } else {
+            valuePerSecond[seconds++] = counter.get();
+        }
+        if (seconds == valuePerSecond.length) {
+            seconds = 0;
+            valuePerMinute[minutes++] = aggregate(valuePerSecond);
+        }
+        if (minutes == valuePerMinute.length) {
+            minutes = 0;
+            valuePerHour[hours++] = aggregate(valuePerMinute);
+        }
+        if (hours == valuePerHour.length) {
+            hours = 0;
+            valuePerWeek[weeks++] = aggregate(valuePerHour);
+        }
+        if (weeks == valuePerWeek.length) {
+            weeks = 0;
+        }
+    }
+
+    //----------------------------------------------------------< TimeSeries >
+
+    public synchronized long[] getValuePerSecond() {
+        return cyclicCopyFrom(valuePerSecond, seconds);
+    }
+
+    public synchronized long[] getValuePerMinute() {
+        return cyclicCopyFrom(valuePerMinute, minutes);
+    }
+
+    public synchronized long[] getValuePerHour() {
+        return cyclicCopyFrom(valuePerHour, hours);
+    }
+
+    public synchronized long[] getValuePerWeek() {
+        return cyclicCopyFrom(valuePerWeek, weeks);
+    }
+
+    //-------------------------------------------------------------< private >
+
+    /**
+     * Returns the sum of all entries in the given array.
+     *
+     * @param array array to be summed
+     * @return sum of entries
+     */
+    private long aggregate(long[] array) {
+        long sum = 0;
+        for (int i = 0; i < array.length; i++) {
+
+            sum += array[i];
+        }
+        if (resetValueEachSecond) {
+            return sum;
+        }
+        return sum / array.length;
+    }
+
+    /**
+     * Returns a copy of the given cyclical array, with the element at
+     * the given position as the first element of the returned array.
+     *
+     * @param array cyclical array
+     * @param pos position of the first element
+     * @return copy of the array
+     */
+    private static long[] cyclicCopyFrom(long[] array, int pos) {
+        long[] reverse = new long[array.length];
+        for (int i = 0; i < array.length; i++) {
+            reverse[i] = array[(pos + i) % array.length];
+        }
+        return reverse;
+    }
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/TimeSeriesStatsUtil.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/TimeSeriesStatsUtil.java
new file mode 100644
index 0000000..afc950f
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/TimeSeriesStatsUtil.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.stats;
+
+import javax.management.openmbean.ArrayType;
+import javax.management.openmbean.CompositeData;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+
+import org.apache.jackrabbit.api.stats.TimeSeries;
+
+/**
+ * Utility class for retrieving {@link javax.management.openmbean.CompositeData} for
+ * {@link org.apache.jackrabbit.api.stats.TimeSeries}.
+ */
+public final class TimeSeriesStatsUtil {
+    public static final String[] ITEM_NAMES = new String[] {"per second", "per minute", "per hour", "per week"};
+
+    private TimeSeriesStatsUtil() {
+    }
+
+    public static CompositeData asCompositeData(TimeSeries timeSeries, String name) {
+        try {
+            long[][] values = new long[][] {timeSeries.getValuePerSecond(), timeSeries.getValuePerMinute(),
+                    timeSeries.getValuePerHour(), timeSeries.getValuePerWeek()};
+            return new CompositeDataSupport(getCompositeType(name), ITEM_NAMES, values);
+        } catch (Exception e) {
+            throw new IllegalArgumentException("Error creating CompositeData instance from TimeSeries", e);
+        }
+    }
+
+    private static CompositeType getCompositeType(String name) throws OpenDataException {
+        ArrayType<int[]> longArrayType = new ArrayType<int[]>(SimpleType.LONG, true);
+        OpenType<?>[] itemTypes = new OpenType[] {longArrayType, longArrayType, longArrayType, longArrayType};
+        return new CompositeType(name, name + " time series", ITEM_NAMES, ITEM_NAMES, itemTypes);
+    }
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/jmx/QueryStatManager.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/jmx/QueryStatManager.java
new file mode 100644
index 0000000..d1b7bf3
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/jmx/QueryStatManager.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.stats.jmx;
+
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.CompositeType;
+import javax.management.openmbean.OpenDataException;
+import javax.management.openmbean.OpenType;
+import javax.management.openmbean.SimpleType;
+import javax.management.openmbean.TabularData;
+import javax.management.openmbean.TabularDataSupport;
+import javax.management.openmbean.TabularType;
+
+import org.apache.jackrabbit.api.jmx.QueryStatManagerMBean;
+import org.apache.jackrabbit.api.stats.QueryStat;
+import org.apache.jackrabbit.api.stats.QueryStatDto;
+
+/**
+ * The QueryStatManagerMBean default implementation
+ * 
+ */
+public class QueryStatManager implements QueryStatManagerMBean {
+
+    private final QueryStat queryStat;
+
+    public QueryStatManager(final QueryStat queryStat) {
+        this.queryStat = queryStat;
+    }
+
+    public boolean isEnabled() {
+        return this.queryStat.isEnabled();
+    }
+
+    public void enable() {
+        this.queryStat.setEnabled(true);
+    }
+
+    public void disable() {
+        this.queryStat.setEnabled(false);
+    }
+
+    public void reset() {
+        this.queryStat.reset();
+    }
+
+    public int getSlowQueriesQueueSize() {
+        return queryStat.getSlowQueriesQueueSize();
+    }
+
+    public void setSlowQueriesQueueSize(int size) {
+        this.queryStat.setSlowQueriesQueueSize(size);
+    }
+
+    public void clearSlowQueriesQueue() {
+        this.queryStat.clearSlowQueriesQueue();
+    }
+
+    public int getPopularQueriesQueueSize() {
+        return queryStat.getPopularQueriesQueueSize();
+    }
+
+    public void setPopularQueriesQueueSize(int size) {
+        queryStat.setPopularQueriesQueueSize(size);
+    }
+
+    public void clearPopularQueriesQueue() {
+        queryStat.clearPopularQueriesQueue();
+    }
+
+    public TabularData getSlowQueries() {
+        return asTabularData(queryStat.getSlowQueries());
+    }
+
+    public TabularData getPopularQueries() {
+        return asTabularData(queryStat.getPopularQueries());
+    }
+
+    private TabularData asTabularData(QueryStatDto[] data) {
+        TabularDataSupport tds = null;
+        try {
+            CompositeType ct = QueryStatCompositeTypeFactory.getCompositeType();
+
+            TabularType tt = new TabularType(QueryStatDto.class.getName(),
+                    "Query History", ct, QueryStatCompositeTypeFactory.index);
+            tds = new TabularDataSupport(tt);
+
+            for (QueryStatDto q : data) {
+                tds.put(new CompositeDataSupport(ct,
+                        QueryStatCompositeTypeFactory.names,
+                        QueryStatCompositeTypeFactory.getValues(q)));
+            }
+            return tds;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    private static class QueryStatCompositeTypeFactory {
+
+        private final static String[] index = { "position" };
+
+        private final static String[] names = { "position", "duration",
+                "occurrenceCount", "language", "statement", "creationTime" };
+
+        private final static String[] descriptions = { "position", "duration",
+                "occurrenceCount", "language", "statement", "creationTime" };
+
+        private final static OpenType[] types = { SimpleType.LONG,
+                SimpleType.LONG, SimpleType.INTEGER, SimpleType.STRING,
+                SimpleType.STRING, SimpleType.STRING };
+
+        public static CompositeType getCompositeType() throws OpenDataException {
+            return new CompositeType(QueryStat.class.getName(),
+                    QueryStat.class.getName(), names, descriptions, types);
+        }
+
+        public static Object[] getValues(QueryStatDto q) {
+            return new Object[] { q.getPosition(), q.getDuration(),
+                    q.getOccurrenceCount(), q.getLanguage(), q.getStatement(),
+                    q.getCreationTime() };
+        }
+    }
+
+}
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/package-info.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/package-info.java
new file mode 100644
index 0000000..add0e63
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/stats/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ at aQute.bnd.annotation.Version("2.7.5")
+package org.apache.jackrabbit.stats;
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/Base64.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/Base64.java
index c3ab069..85d851a 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/Base64.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/Base64.java
@@ -234,7 +234,7 @@ public class Base64 {
      *
      * @since Apache Jackrabbit 2.3
      * @param data the string to be encoded
-     * @param base64-encoding of the string
+     * @return base64-encoding of the string
      */
     public static String encode(String data) {
         try {
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ChildrenCollectorFilter.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ChildrenCollectorFilter.java
index 9840c62..3443b77 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ChildrenCollectorFilter.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ChildrenCollectorFilter.java
@@ -24,12 +24,12 @@ import javax.jcr.PropertyIterator;
 import javax.jcr.RepositoryException;
 import javax.jcr.util.TraversingItemVisitor;
 
+import org.apache.jackrabbit.commons.ItemNameMatcher;
 import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter;
 import org.apache.jackrabbit.commons.iterator.PropertyIteratorAdapter;
 
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.StringTokenizer;
 
 /**
  * <code>ChildrenCollectorFilter</code> is a utility class
@@ -127,6 +127,7 @@ public class ChildrenCollectorFilter extends TraversingItemVisitor.Default {
     /**
      * {@inheritDoc}
      */
+    @Override
     protected void entering(Node node, int level)
             throws RepositoryException {
         if (level > 0 && collectNodes) {
@@ -145,6 +146,7 @@ public class ChildrenCollectorFilter extends TraversingItemVisitor.Default {
     /**
      * {@inheritDoc}
      */
+    @Override
     protected void entering(Property property, int level)
             throws RepositoryException {
         if (level > 0 && collectProperties) {
@@ -161,136 +163,20 @@ public class ChildrenCollectorFilter extends TraversingItemVisitor.Default {
     }
 
     /**
-     * Matches the name pattern against the specified name.
-     * <p/>
-     * The pattern may be a full name or a partial name with one or more
-     * wildcard characters ("*"), or a disjunction (using the "|" character
-     * to represent logical <i>OR</i>) of these. For example,
-     * <p/>
-     * <code>"jcr:*|foo:bar"</code>
-     * <p/>
-     * would match
-     * <p/>
-     * <code>"foo:bar"</code>, but also <code>"jcr:whatever"</code>.
-     * <p/>
-     * <pre>
-     * The EBNF for pattern is:
+     * Same as {@link ItemNameMatcher#matches(String, String)}.
      *
-     * namePattern ::= disjunct {'|' disjunct}
-     * disjunct ::= name [':' name]
-     * name ::= '*' |
-     *          ['*'] fragment {'*' fragment}['*']
-     * fragment ::= char {char}
-     * char ::= nonspace | ' '
-     * nonspace ::= (* Any Unicode character except:
-     *               '/', ':', '[', ']', '*',
-     *               ''', '"', '|' or any whitespace
-     *               character *)
-     * </pre>
-     * Note that leading and trailing whitespace around a pattern <i>is</i> ignored.
-     *
-     * @param name the name to test the pattern with
-     * @param pattern the pattern to be matched against the name
-     * @return true if the specified name matches the pattern
      * @see javax.jcr.Node#getNodes(String)
      */
     public static boolean matches(String name, String pattern) {
-        // split pattern
-        StringTokenizer st = new StringTokenizer(pattern, OR, false);
-        while (st.hasMoreTokens()) {
-            // remove leading & trailing whitespace from token
-            String token = st.nextToken().trim();
-            if (internalMatches(name, token, 0, 0)) {
-                return true;
-            }
-        }
-        return false;
+        return ItemNameMatcher.matches(name, pattern);
     }
 
     /**
-     * Matches the <code>nameGlob</code> strings in the passed array against
-     * the specified name.
-     * <p>
-     * A glob may be a full name or a partial name with one or more
-     * wildcard characters ("<code>*</code>").
-     * <p>
-     * Note that unlike in the case of the {@link #matches(String, String)}
-     * leading and trailing whitespace around a glob is <i>not</i> ignored.
+     * Same as {@link ItemNameMatcher#matches(String, String)}.
      *
-     * @param name the name to test the pattern with
-     * @param nameGlobs an array of globbing strings
-     * @return true if the specified name matches any of the globs
-     * @see javax.jcr.Node#getNodes(String[])
+     * @see javax.jcr.Node#getNodes(String)
      */
     public static boolean matches(String name, String[] nameGlobs) {
-        for (int i = 0; i < nameGlobs.length; i++) {
-            // use globbing string as-is, i.e. don't trim any leading/trailing
-            // whitespace
-            if (internalMatches(name, nameGlobs[i], 0, 0)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Internal helper used to recursively match the pattern
-     *
-     * @param s       The string to be tested
-     * @param pattern The pattern
-     * @param sOff    offset within <code>s</code>
-     * @param pOff    offset within <code>pattern</code>.
-     * @return true if <code>s</code> matched pattern, else false.
-     */
-    private static boolean internalMatches(String s, String pattern,
-                                           int sOff, int pOff) {
-        int pLen = pattern.length();
-        int sLen = s.length();
-
-        while (true) {
-            if (pOff >= pLen) {
-                if (sOff >= sLen) {
-                    return true;
-                } else if (s.charAt(sOff) == '[') {
-                    // check for subscript notation (e.g. "whatever[1]")
-
-                    // the entire pattern matched up to the subscript:
-                    // -> ignore the subscript
-                    return true;
-                } else {
-                    return false;
-                }
-            }
-            if (sOff >= sLen && pattern.charAt(pOff) != WILDCARD_CHAR) {
-                return false;
-            }
-
-            // check for a '*' as the next pattern char;
-            // this is handled by a recursive call for
-            // each postfix of the name.
-            if (pattern.charAt(pOff) == WILDCARD_CHAR) {
-                if (++pOff >= pLen) {
-                    return true;
-                }
-
-                while (true) {
-                    if (internalMatches(s, pattern, sOff, pOff)) {
-                        return true;
-                    }
-                    if (sOff >= sLen) {
-                        return false;
-                    }
-                    sOff++;
-                }
-            }
-
-            if (pOff < pLen && sOff < sLen) {
-                if (pattern.charAt(pOff) != s.charAt(sOff)) {
-                    return false;
-                }
-            }
-            pOff++;
-            sOff++;
-        }
+        return ItemNameMatcher.matches(name, nameGlobs);
     }
 }
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ISO8601.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ISO8601.java
index 7ec6bcf..50cdf4b 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ISO8601.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ISO8601.java
@@ -18,13 +18,15 @@ package org.apache.jackrabbit.util;
 
 import java.util.Calendar;
 import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.TimeZone;
 
 /**
  * The <code>ISO8601</code> utility class provides helper methods
  * to deal with date/time formatting using a specific ISO8601-compliant
  * format (see <a href="http://www.w3.org/TR/NOTE-datetime">ISO 8601</a>).
- * <p/>
+ * <p>
  * The currently supported format is:
  * <pre>
  *   ±YYYY-MM-DDThh:mm:ss.SSSTZD
@@ -46,6 +48,33 @@ import java.util.TimeZone;
  * </pre>
  */
 public final class ISO8601 {
+
+    /**
+     * Flyweight instances of known time zones.
+     */
+    private static final Map<String, TimeZone> TZS =
+            new HashMap<String, TimeZone>();
+
+    static {
+        TimeZone gmt = TimeZone.getTimeZone("GMT");
+        TZS.put("Z", gmt);
+        TZS.put("+00:00", gmt);
+        TZS.put("-00:00", gmt);
+
+        // http://en.wikipedia.org/wiki/List_of_UTC_time_offsets
+        String[] tzs = {
+                "-12:00", "-11:00", "-10:00", "-09:30", "-09:00", "-08:00",
+                "-07:00", "-06:00", "-05:00", "-04:30", "-04:00", "-03:30",
+                "-03:00", "-02:00", "-01:00", "+01:00", "+02:00", "+03:00",
+                "+03:30", "+04:00", "+04:30", "+05:00", "+05:30", "+05:45",
+                "+06:00", "+06:30", "+07:00", "+08:00", "+08:45", "+09:00",
+                "+09:30", "+10:00", "+10:30", "+11:00", "+11:30", "+12:00",
+                "+12:45", "+13:00", "+14:00" };
+        for (String tz : tzs) {
+            TZS.put(tz, TimeZone.getTimeZone("GMT" + tz));
+        }
+    }
+
     /**
      * Parses an ISO8601-compliant date/time string.
      *
@@ -82,7 +111,7 @@ public final class ISO8601 {
          */
 
         int year, month, day, hour, min, sec, ms;
-        String tzID;
+        TimeZone tz;
         try {
             // year (YYYY)
             year = Integer.parseInt(text.substring(start, start + 4));
@@ -136,14 +165,17 @@ public final class ISO8601 {
             ms = Integer.parseInt(text.substring(start, start + 3));
             start += 3;
             // time zone designator (Z or +00:00 or -00:00)
-            if (text.charAt(start) == '+' || text.charAt(start) == '-') {
+            String tzid = text.substring(start);
+            tz = TZS.get(tzid);
+            if (tz == null) {
                 // offset to UTC specified in the format +00:00/-00:00
-                tzID = "GMT" + text.substring(start);
-            } else if (text.substring(start).equals("Z")) {
-                tzID = "GMT";
-            } else {
-                // invalid time zone designator
-                return null;
+                tzid = "GMT" + tzid;
+                tz = TimeZone.getTimeZone(tzid);
+                // verify id of returned time zone (getTimeZone defaults to "GMT")
+                if (!tz.getID().equals(tzid)) {
+                    // invalid time zone
+                    return null;
+                }
             }
         } catch (IndexOutOfBoundsException e) {
             return null;
@@ -151,13 +183,6 @@ public final class ISO8601 {
             return null;
         }
 
-        TimeZone tz = TimeZone.getTimeZone(tzID);
-        // verify id of returned time zone (getTimeZone defaults to "GMT")
-        if (!tz.getID().equals(tzID)) {
-            // invalid time zone
-            return null;
-        }
-
         // initialize Calendar object
         Calendar cal = Calendar.getInstance(tz);
         cal.setLenient(false);
@@ -290,7 +315,7 @@ public final class ISO8601 {
 
     /**
      * Appends a zero-padded number to the given string buffer.
-     * <p/>
+     * <p>
      * This is an internal helper method which doesn't perform any
      * validation on the given arguments.
      *
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ISO9075.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ISO9075.java
index fb831bc..5347b11 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ISO9075.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/ISO9075.java
@@ -146,7 +146,7 @@ public class ISO9075 {
      * Encodes the character <code>c</code> as a String in the following form:
      * <code>"_x" + hex value of c + "_"</code>. Where the hex value has
      * four digits if the character with possibly leading zeros.
-     * <p/>
+     * <p>
      * Example: ' ' (the space character) is encoded to: _x0020_
      * @param c the character to encode
      * @param b the encoded character is appended to <code>StringBuffer</code>
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/Locked.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/Locked.java
index a137280..c7dabb8 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/Locked.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/Locked.java
@@ -35,7 +35,7 @@ import javax.jcr.observation.ObservationManager;
  * ensuring that the modification will never fail with an {@link
  * javax.jcr.InvalidItemStateException}. This utility can be used with any
  * JCR Repository, not just Jackrabbit.
- * <p/>
+ * <p>
  * The following example shows how this utility can be used to implement
  * a persistent counter:
  * <pre>
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/Text.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/Text.java
index 45a232a..31f4268 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/Text.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/Text.java
@@ -267,7 +267,7 @@ public class Text {
      * The list of characters that are not encoded by the <code>escape()</code>
      * and <code>unescape()</code> METHODS. They contains the characters as
      * defined 'unreserved' in section 2.3 of the RFC 2396 'URI generic syntax':
-     * <p/>
+     * <p>
      * <pre>
      * unreserved  = alphanum | mark
      * mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
@@ -448,7 +448,7 @@ public class Text {
      * the characters it absolutely needs to in order to make the resulting
      * string a valid JCR name.
      * Use {@link #unescapeIllegalJcrChars(String)} for decoding.
-     * <p/>
+     * <p>
      * QName EBNF:<br>
      * <xmp>
      * simplename ::= onecharsimplename | twocharsimplename | threeormorecharname
@@ -532,7 +532,7 @@ public class Text {
 
     /**
      * Unescapes previously escaped jcr chars.
-     * <p/>
+     * <p>
      * Please note, that this does not exactly the same as the url related
      * {@link #unescape(String)}, since it handles the byte-encoding
      * differently.
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/TransientFileFactory.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/TransientFileFactory.java
index d518f61..80b57a5 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/TransientFileFactory.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/TransientFileFactory.java
@@ -30,9 +30,9 @@ import java.util.Collections;
  * <i>transient</i> files, i.e. temporary files that are automatically
  * removed once the associated <code>File</code> object is reclaimed by the
  * garbage collector.
- * <p/>
+ * <p>
  * File deletion is handled by a low-priority background thread.
- * <p/>
+ * <p>
  */
 public class TransientFileFactory {
 
@@ -126,14 +126,14 @@ public class TransientFileFactory {
 
     /**
      * Shuts this factory down removing all temp files and removes shutdown hook.
-     * <p/>
+     * <p>
      * <b>Warning!!!</b>
-     * <p/>
+     * <p>
      * This should be called by a web-application <b><i>IF</b></i> it is unloaded
      * <b><i>AND IF</i></b> jackrabbit-jcr-commons.jar had been loaded by
      * the webapp classloader. This must be called after all repositories had
      * been stopped, so use with great care!
-     * <p/>
+     * <p>
      * See http://issues.apache.org/jira/browse/JCR-1636 for details.
      */
     public static void shutdown() {
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/WeakIdentityCollection.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/WeakIdentityCollection.java
index 943a8c6..992612e 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/WeakIdentityCollection.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/util/WeakIdentityCollection.java
@@ -27,7 +27,7 @@ import java.lang.ref.ReferenceQueue;
 /**
  * <code>WeakIdentityCollection</code> implements a Collection with weak values.
  * Equality of elements is tested using the == operator.
- * <p/>
+ * <p>
  * This collection does not hide the fact that the garbage collector will remove
  * a mapping at some point in time. Thus, the {@link java.util.Iterator} returned
  * by this collection might return <code>null</code> values. The same applies
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/BinaryImpl.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/BinaryImpl.java
index 4556b74..d79411e 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/BinaryImpl.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/BinaryImpl.java
@@ -60,7 +60,7 @@ public class BinaryImpl implements Binary {
      * <code>InputStream</code>. The contents of the stream is spooled
      * to a temporary file or to a byte buffer if its size is smaller than
      * {@link #MAX_BUFFER_SIZE}.
-     * <p/>
+     * <p>
      * @param in stream to be represented as a <code>BLOBFileValue</code> instance
      * @throws IOException if an error occurs while reading from the stream or
      *                     writing to the temporary file
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/BinaryValue.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/BinaryValue.java
index 0017950..4d05615 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/BinaryValue.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/BinaryValue.java
@@ -82,7 +82,7 @@ public class BinaryValue extends BaseValue {
 
     /**
      * Indicates whether some other object is "equal to" this one.
-     * <p/>
+     * <p>
      * The result is <code>true</code> if and only if the argument is not
      * <code>null</code> and is a <code>BinaryValue</code> object that
      * represents the same value as this object.
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/BooleanValue.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/BooleanValue.java
index a232c11..1f1e269 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/BooleanValue.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/BooleanValue.java
@@ -66,7 +66,7 @@ public class BooleanValue extends BaseValue {
 
     /**
      * Indicates whether some other object is "equal to" this one.
-     * <p/>
+     * <p>
      * The result is <code>true</code> if and only if the argument is not
      * <code>null</code> and is a <code>BooleanValue</code> object that
      * represents the same value as this object.
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/DateValue.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/DateValue.java
index 829a9e9..ea790f7 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/DateValue.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/DateValue.java
@@ -50,7 +50,7 @@ public class DateValue extends BaseValue {
     /**
      * Returns a new <code>DateValue</code> initialized to the value
      * represented by the specified <code>String</code>.
-     * <p/>
+     * <p>
      * The specified <code>String</code> must be a ISO8601-compliant date/time
      * string.
      *
@@ -72,7 +72,7 @@ public class DateValue extends BaseValue {
 
     /**
      * Indicates whether some other object is "equal to" this one.
-     * <p/>
+     * <p>
      * The result is <code>true</code> if and only if the argument is not
      * <code>null</code> and is a <code>DateValue</code> object that
      * represents the same value as this object.
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/DecimalValue.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/DecimalValue.java
index 8cec952..03051f6 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/DecimalValue.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/DecimalValue.java
@@ -63,7 +63,7 @@ public class DecimalValue extends BaseValue {
 
     /**
      * Indicates whether some other object is "equal to" this one.
-     * <p/>
+     * <p>
      * The result is <code>true</code> if and only if the argument is not
      * <code>null</code> and is a <code>DecimalValue</code> object that
      * represents the same value as this object.
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/DoubleValue.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/DoubleValue.java
index a09fb9b..2a158ee 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/DoubleValue.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/DoubleValue.java
@@ -73,7 +73,7 @@ public class DoubleValue extends BaseValue {
 
     /**
      * Indicates whether some other object is "equal to" this one.
-     * <p/>
+     * <p>
      * The result is <code>true</code> if and only if the argument is not
      * <code>null</code> and is a <code>DoubleValue</code> object that
      * represents the same value as this object.
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/LongValue.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/LongValue.java
index e21f6ee..5255151 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/LongValue.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/LongValue.java
@@ -73,7 +73,7 @@ public class LongValue extends BaseValue {
 
     /**
      * Indicates whether some other object is "equal to" this one.
-     * <p/>
+     * <p>
      * The result is <code>true</code> if and only if the argument is not
      * <code>null</code> and is a <code>LongValue</code> object that
      * represents the same value as this object.
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/NameValue.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/NameValue.java
index aa29c37..a1b7518 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/NameValue.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/NameValue.java
@@ -36,7 +36,7 @@ public class NameValue extends BaseValue {
     /**
      * Returns a new <code>NameValue</code> initialized to the value
      * represented by the specified <code>String</code>.
-     * <p/>
+     * <p>
      * The specified <code>String</code> must be a valid JCR name.
      *
      * @param s the string to be parsed.
@@ -68,7 +68,7 @@ public class NameValue extends BaseValue {
 
     /**
      * Indicates whether some other object is "equal to" this one.
-     * <p/>
+     * <p>
      * The result is <code>true</code> if and only if the argument is not
      * <code>null</code> and is a <code>NameValue</code> object that
      * represents the same value as this object.
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/PathValue.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/PathValue.java
index d6d6bee..8d7b310 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/PathValue.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/PathValue.java
@@ -36,7 +36,7 @@ public class PathValue extends BaseValue {
     /**
      * Returns a new <code>PathValue</code> initialized to the value
      * represented by the specified <code>String</code>.
-     * <p/>
+     * <p>
      * The specified <code>String</code> must be a valid absolute or relative
      * path.
      *
@@ -68,7 +68,7 @@ public class PathValue extends BaseValue {
 
     /**
      * Indicates whether some other object is "equal to" this one.
-     * <p/>
+     * <p>
      * The result is <code>true</code> if and only if the argument is not
      * <code>null</code> and is a <code>PathValue</code> object that
      * represents the same value as this object.
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/ReferenceValue.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/ReferenceValue.java
index 6eeadc0..0ffd86c 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/ReferenceValue.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/ReferenceValue.java
@@ -56,7 +56,7 @@ public class ReferenceValue extends BaseValue {
     /**
      * Returns a new <code>ReferenceValue</code> initialized to the value
      * represented by the specified <code>String</code>.
-     * <p/>
+     * <p>
      * The specified <code>String</code> must denote the UUID of an existing
      * node.
      *
@@ -93,7 +93,7 @@ public class ReferenceValue extends BaseValue {
 
     /**
      * Indicates whether some other object is "equal to" this one.
-     * <p/>
+     * <p>
      * The result is <code>true</code> if and only if the argument is not
      * <code>null</code> and is a <code>ReferenceValue</code> object that
      * represents the same value as this object.
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/StringValue.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/StringValue.java
index 34b24f2..f8eae38 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/StringValue.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/StringValue.java
@@ -41,7 +41,7 @@ public class StringValue extends BaseValue {
 
     /**
      * Indicates whether some other object is "equal to" this one.
-     * <p/>
+     * <p>
      * The result is <code>true</code> if and only if the argument is not
      * <code>null</code> and is a <code>StringValue</code> object that
      * represents the same value as this object.
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/URIValue.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/URIValue.java
index d443f8e..be919e8 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/URIValue.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/URIValue.java
@@ -38,7 +38,7 @@ public class URIValue extends BaseValue {
     /**
      * Returns a new <code>URIValue</code> initialized to the value
      * represented by the specified <code>String</code>.
-     * <p/>
+     * <p>
      * The specified <code>String</code> must be a valid URI.
      *
      * @param s the string to be parsed.
@@ -72,7 +72,7 @@ public class URIValue extends BaseValue {
 
     /**
      * Indicates whether some other object is "equal to" this one.
-     * <p/>
+     * <p>
      * The result is <code>true</code> if and only if the argument is not
      * <code>null</code> and is a <code>URIValue</code> object that
      * represents the same value as this object.
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/ValueHelper.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/ValueHelper.java
index 3ec9e0e..5e26e87 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/ValueHelper.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/ValueHelper.java
@@ -16,6 +16,8 @@
  */
 package org.apache.jackrabbit.value;
 
+import static javax.jcr.PropertyType.UNDEFINED;
+
 import org.apache.jackrabbit.util.Base64;
 import org.apache.jackrabbit.util.Text;
 import org.apache.jackrabbit.util.TransientFileFactory;
@@ -818,4 +820,29 @@ public class ValueHelper {
             return convert(value, type, factory);
         }
     }
+
+    /**
+     * Determine the {@link javax.jcr.PropertyType} of the passed values if all are of
+     * the same type.
+     *
+     * @param values array of values of the same type
+     * @return  {@link javax.jcr.PropertyType#UNDEFINED} if {@code values} is empty,
+     *          {@code values[0].getType()} otherwise.
+     * @throws javax.jcr.ValueFormatException  if not all {@code values} are of the same type
+     */
+    public static int getType(Value[] values) throws ValueFormatException {
+        int type = UNDEFINED;
+        for (Value value : values) {
+            if (value != null) {
+                if (type == UNDEFINED) {
+                    type = value.getType();
+                } else if (value.getType() != type) {
+                    throw new ValueFormatException(
+                            "All values of a multi-valued property must be of the same type");
+                }
+            }
+        }
+        return type;
+    }
+
 }
diff --git a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/WeakReferenceValue.java b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/WeakReferenceValue.java
index cf611ef..f971813 100644
--- a/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/WeakReferenceValue.java
+++ b/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/value/WeakReferenceValue.java
@@ -56,7 +56,7 @@ public class WeakReferenceValue extends BaseValue {
     /**
      * Returns a new <code>ReferenceValue</code> initialized to the value
      * represented by the specified <code>String</code>.
-     * <p/>
+     * <p>
      * The specified <code>String</code> must denote the UUID of an existing
      * node.
      *
@@ -93,7 +93,7 @@ public class WeakReferenceValue extends BaseValue {
 
     /**
      * Indicates whether some other object is "equal to" this one.
-     * <p/>
+     * <p>
      * The result is <code>true</code> if and only if the argument is not
      * <code>null</code> and is a <code>ReferenceValue</code> object that
      * represents the same value as this object.
diff --git a/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/JcrUtilsTest.java b/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/JcrUtilsTest.java
index c3d8f42..007bef3 100644
--- a/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/JcrUtilsTest.java
+++ b/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/JcrUtilsTest.java
@@ -16,8 +16,10 @@
  */
 package org.apache.jackrabbit.commons;
 
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Hashtable;
+import java.util.Iterator;
 import java.util.Map;
 
 import javax.jcr.PropertyType;
@@ -76,4 +78,19 @@ public class JcrUtilsTest extends MockCase {
         assertEquals(PropertyType.DATE, JcrUtils.getPropertyType(
                 PropertyType.TYPENAME_DATE.toUpperCase()));
     }
+
+    public void testIn() {
+        Iterable<String> iterable = JcrUtils.in(Arrays.asList("A").iterator());
+        Iterator<String> iterator = iterable.iterator();
+        assertNotNull(iterator);
+        assertTrue(iterator.hasNext());
+        assertEquals("A", iterator.next());
+        assertFalse(iterator.hasNext());
+
+        try {
+            iterable.iterator();
+            fail("Second execution of Iterable.iterator() should throw an exception");
+        } catch (IllegalStateException expected) {
+        }
+    }
 }
diff --git a/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/json/JsonUtilTest.java b/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/json/JsonUtilTest.java
index 35cdc0a..a61e668 100644
--- a/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/json/JsonUtilTest.java
+++ b/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/json/JsonUtilTest.java
@@ -16,41 +16,43 @@
  */
 package org.apache.jackrabbit.commons.json;
 
-import junit.framework.TestCase;
-
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.Map;
 
+import junit.framework.TestCase;
+
 /**
  * <code>JSONUtilTest</code>...
  */
 public class JsonUtilTest extends TestCase {
 
     public void testGetJsonString() {
-        Map m = new HashMap();
-        m.put("abc", "\"abc\"");
-        m.put("a \"b\" c", "\"a \\\"b\\\" c\"");
-        m.put("a\tb\rc\nd\fe\b", "\"a\\tb\\rc\\nd\\fe\\b\"");
-        m.put("\\abc", "\"\\\\abc\"");
-        m.put("abc", "\"abc\"");
+        char DQ = '"';
+
+        Map<String, String> m = new HashMap<String, String>();
+        m.put("abc", "abc");
+        m.put("a \"b\" c", "a \\\"b\\\" c");
+        m.put("a\tb\rc\nd\fe\b", "a\\tb\\rc\\nd\\fe\\b");
+        m.put("\\abc", "\\\\abc");
+        m.put("abc", "abc");
 
         // non-printable ascii other than those treated (\t,\r,\n)
-        m.put(String.valueOf((char) 7), "\"\\u0007\"");
-        m.put(String.valueOf((char) 30), "\"\\u001e\"");
+        m.put(String.valueOf((char) 7), "\\u0007");
+        m.put(String.valueOf((char) 30), "\\u001e");
 
-        // chinese
-        m.put("\u4e00a\u4e8cb\u4e09c", "\"\u4e00a\u4e8cb\u4e09c\"");
+        /* chinese */
+        m.put("\u4e00a\u4e8cb\u4e09c", "\u4e00a\u4e8cb\u4e09c");
         /* arabic */
-        m.put("\u062c\u062f\u064a\u062f", "\"\u062c\u062f\u064a\u062f\"");
-        /* �a�b?c */
-        m.put("\u00d1a\u00e7b\u0416c", "\"\u00d1a\u00e7b\u0416c\"");
-        // ����
-        // m.put("����", "\"\u00e2\u00e8\u00f8\u00fc\"");
-
-        for (Iterator it = m.keySet().iterator(); it.hasNext();) {
-            String key = it.next().toString();
-            assertEquals(m.get(key).toString(), JsonUtil.getJsonString(key));
+        m.put("\u062c\u062f\u064a\u062f", "\u062c\u062f\u064a\u062f");
+        /* U+00D1 a U+00E7 b U+0416 c */
+        m.put("\u00d1a\u00e7b\u0416c", "\u00d1a\u00e7b\u0416c");
+        /* U+00E2 a U+00E8 b U+00F8 c U+00FC d */
+        m.put("\u00e2a\u00e8b\u00f8c\u00fcd", "\u00e2a\u00e8b\u00f8c\u00fcd");
+
+        for (Map.Entry<String, String> t : m.entrySet()) {
+            String input = t.getKey();
+            String output = DQ + t.getValue() + DQ;
+            assertEquals(output, JsonUtil.getJsonString(input));
         }
     }
 }
\ No newline at end of file
diff --git a/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/query/GQLTest.java b/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/query/GQLTest.java
new file mode 100644
index 0000000..1c6e98f
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/commons/query/GQLTest.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.commons.query;
+
+import javax.jcr.RepositoryException;
+
+import junit.framework.TestCase;
+
+public class GQLTest extends TestCase {
+    
+    public void testGQL() throws RepositoryException {
+        assertEquals(
+                "//*[jcr:like(fn:lower-case(fn:name()), 'test')] ", 
+                GQL.translateToXPath(
+                        "order:- " +
+                        "name:test", null, "assets"));
+        assertEquals(
+                "//*[1=1] order by @jcr:score descending", 
+                GQL.translateToXPath(
+                        "\"jcr:nativeXPath\":\"1=1\"", null, "assets"));
+        assertEquals(
+                "//*[(jcr:contains(assets/@a, '1') and 1=1)] ", 
+                GQL.translateToXPath(
+                        "order:- " +
+                        "a: 1 " +
+                        "\"jcr:nativeXPath\":\"1=1\"", null, "assets"));
+        
+    }
+
+}
diff --git a/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/stats/RepositoryStatisticsImplTest.java b/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/stats/RepositoryStatisticsImplTest.java
new file mode 100644
index 0000000..15fc029
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/stats/RepositoryStatisticsImplTest.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.stats;
+
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+import junit.framework.TestCase;
+import org.apache.jackrabbit.api.stats.TimeSeries;
+
+public class RepositoryStatisticsImplTest extends TestCase {
+
+    private static final int DEFAULT_NUMBER_OF_ELEMENTS = 20;
+
+    public void testDefaultIterator() {
+        RepositoryStatisticsImpl repositoryStatistics = new RepositoryStatisticsImpl();
+
+        Iterator<Entry<String, TimeSeries>> iterator = repositoryStatistics.iterator();
+        int count = 0;
+        while (iterator.hasNext()) {
+            count++;
+            iterator.next();
+        }
+        assertEquals(DEFAULT_NUMBER_OF_ELEMENTS, count);
+    }
+
+    public void testIteratorWithSingleCustomType() {
+        RepositoryStatisticsImpl repositoryStatistics = new RepositoryStatisticsImpl();
+        String type = "customType";
+        repositoryStatistics.getCounter(type, false);
+
+        Iterator<Entry<String, TimeSeries>> iterator = repositoryStatistics.iterator();
+        int count = 0;
+        boolean customTypeExists = false;
+        while (iterator.hasNext()) {
+            count++;
+            Entry<String, TimeSeries> entry = iterator.next();
+            if (type.equals(entry.getKey())) {
+                customTypeExists = true;
+            }
+        }
+        assertEquals(DEFAULT_NUMBER_OF_ELEMENTS + 1, count);
+        assertTrue(customTypeExists);
+    }
+}
diff --git a/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/stats/TimeSeriesRecorderTest.java b/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/stats/TimeSeriesRecorderTest.java
new file mode 100644
index 0000000..5bf4fb5
--- /dev/null
+++ b/jackrabbit-jcr-commons/src/test/java/org/apache/jackrabbit/stats/TimeSeriesRecorderTest.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.stats;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import junit.framework.TestCase;
+import org.apache.jackrabbit.api.stats.RepositoryStatistics;
+
+public class TimeSeriesRecorderTest extends TestCase {
+
+    public void testCounter() {
+        TimeSeriesRecorder recorder = new TimeSeriesRecorder(
+                RepositoryStatistics.Type.SESSION_READ_COUNTER);
+        AtomicLong counter = recorder.getCounter();
+
+        // initial values
+        assertValues(recorder.getValuePerSecond());
+        assertValues(recorder.getValuePerMinute());
+        assertValues(recorder.getValuePerHour());
+        assertValues(recorder.getValuePerWeek());
+
+        // no changes in first second
+        recorder.recordOneSecond();
+        assertValues(recorder.getValuePerSecond());
+        assertValues(recorder.getValuePerMinute());
+        assertValues(recorder.getValuePerHour());
+        assertValues(recorder.getValuePerWeek());
+
+        // one increment in second
+        counter.incrementAndGet();
+        recorder.recordOneSecond();
+        assertValues(recorder.getValuePerSecond(), 1);
+        assertValues(recorder.getValuePerMinute());
+        assertValues(recorder.getValuePerHour());
+        assertValues(recorder.getValuePerWeek());
+
+        // two increments in second
+        counter.incrementAndGet();
+        counter.incrementAndGet();
+        recorder.recordOneSecond();
+        assertValues(recorder.getValuePerSecond(), 2, 1);
+        assertValues(recorder.getValuePerMinute());
+        assertValues(recorder.getValuePerHour());
+        assertValues(recorder.getValuePerWeek());
+
+        // no changes in a second
+        recorder.recordOneSecond();
+        assertValues(recorder.getValuePerSecond(), 0, 2, 1);
+        assertValues(recorder.getValuePerMinute());
+        assertValues(recorder.getValuePerHour());
+        assertValues(recorder.getValuePerWeek());
+
+        // ten increments in a second
+        counter.addAndGet(10);
+        recorder.recordOneSecond();
+        assertValues(recorder.getValuePerSecond(), 10, 0, 2, 1);
+        assertValues(recorder.getValuePerMinute());
+        assertValues(recorder.getValuePerHour());
+        assertValues(recorder.getValuePerWeek());
+
+        // one minute
+        for (int i = 0; i < 60; i++) {
+            recorder.recordOneSecond();
+        }
+        assertValues(recorder.getValuePerSecond());
+        assertValues(recorder.getValuePerMinute(), 13);
+        assertValues(recorder.getValuePerHour());
+        assertValues(recorder.getValuePerWeek());
+
+        // second minute
+        for (int i = 0; i < 60; i++) {
+            recorder.recordOneSecond();
+        }
+        assertValues(recorder.getValuePerSecond());
+        assertValues(recorder.getValuePerMinute(), 0, 13);
+        assertValues(recorder.getValuePerHour());
+        assertValues(recorder.getValuePerWeek());
+
+        // one hour
+        for (int i = 0; i < 60 * 60; i++) {
+            recorder.recordOneSecond();
+        }
+        assertValues(recorder.getValuePerSecond());
+        assertValues(recorder.getValuePerMinute());
+        assertValues(recorder.getValuePerHour(), 13);
+        assertValues(recorder.getValuePerWeek());
+
+        // one week
+        for (int i = 0; i < 7 * 24 * 60 * 60; i++) {
+            recorder.recordOneSecond();
+        }
+        assertValues(recorder.getValuePerSecond());
+        assertValues(recorder.getValuePerMinute());
+        assertValues(recorder.getValuePerHour());
+        assertValues(recorder.getValuePerWeek(), 13);
+    }
+
+    private void assertValues(long[] values, long... expected) {
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals(expected[i], values[values.length - i - 1]);
+        }
+        for (int i = expected.length; i < values.length; i++) {
+            assertEquals(0, values[values.length - i - 1]);
+        }
+    }
+
+}
diff --git a/jackrabbit-jcr-rmi/pom.xml b/jackrabbit-jcr-rmi/pom.xml
index e1ddacd..a5fba15 100644
--- a/jackrabbit-jcr-rmi/pom.xml
+++ b/jackrabbit-jcr-rmi/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>jackrabbit-parent</artifactId>
-    <version>2.3.6</version>
+    <version>2.10.1</version>
     <relativePath>../jackrabbit-parent/pom.xml</relativePath>
   </parent>
 
@@ -149,53 +149,15 @@
 org.apache.jackrabbit.test.api.ShareableNodeTest
 org.apache.jackrabbit.test.api.SetValueValueFormatExceptionTest#testNodeNotReferenceable
 
-org.apache.jackrabbit.test.api.LifecycleTest#testFollowLifecycleTransition
-org.apache.jackrabbit.test.api.LifecycleTest#testGetAllowedLifecycleTransitions
 org.apache.jackrabbit.test.api.NameTest#testExpandedNameValue
 org.apache.jackrabbit.test.api.NameTest#testExpandedNameValueProperty
+
 org.apache.jackrabbit.test.api.NodeRemoveMixinTest#testNotAssigned
-org.apache.jackrabbit.test.api.RepositoryDescriptorTest#testGetDescriptorValues
-org.apache.jackrabbit.test.api.RepositoryDescriptorTest#testRequiredDescriptors
 org.apache.jackrabbit.test.api.ValueFactoryTest#testValueFormatException
-org.apache.jackrabbit.test.api.version.WorkspaceRestoreTest#testWorkspaceRestoreWithParent
-org.apache.jackrabbit.test.api.version.WorkspaceRestoreTest#testWorkspaceRestoreWithParentJcr2
 
-org.apache.jackrabbit.test.api.AddNodeTest#testAbstractNodeType
 org.apache.jackrabbit.test.api.GetWeakReferencesTest#testMultiValues
-org.apache.jackrabbit.test.api.GetWeakReferencesTest#testMultiValuesWithName
-org.apache.jackrabbit.test.api.GetWeakReferencesTest#testNonReferenceable
 org.apache.jackrabbit.test.api.GetWeakReferencesTest#testSingleValue
-org.apache.jackrabbit.test.api.GetWeakReferencesTest#testSingleValueWithName
-org.apache.jackrabbit.test.api.lock.DeepLockTest#testGetSecondsRemaining
-org.apache.jackrabbit.test.api.lock.DeepLockTest#testGetSecondsRemainingAfterUnlock
-org.apache.jackrabbit.test.api.lock.DeepLockTest#testIsLockOwningSession
-org.apache.jackrabbit.test.api.lock.DeepLockTest#testLockExpiration
-org.apache.jackrabbit.test.api.lock.OpenScopedLockTest#testGetSecondsRemaining
-org.apache.jackrabbit.test.api.lock.OpenScopedLockTest#testGetSecondsRemainingAfterUnlock
-org.apache.jackrabbit.test.api.lock.OpenScopedLockTest#testIsLockOwningSession
-org.apache.jackrabbit.test.api.lock.OpenScopedLockTest#testLockExpiration
-org.apache.jackrabbit.test.api.lock.SessionScopedLockTest#testGetSecondsRemaining
-org.apache.jackrabbit.test.api.lock.SessionScopedLockTest#testGetSecondsRemainingAfterUnlock
-org.apache.jackrabbit.test.api.lock.SessionScopedLockTest#testIsLockOwningSession
-org.apache.jackrabbit.test.api.lock.SessionScopedLockTest#testLockExpiration
-org.apache.jackrabbit.test.api.NamePropertyTest#testGetNode
-org.apache.jackrabbit.test.api.NodeSetPrimaryTypeTest#testAddNonExisting
-org.apache.jackrabbit.test.api.NodeSetPrimaryTypeTest#testCheckedIn
-org.apache.jackrabbit.test.api.NodeSetPrimaryTypeTest#testLocked
-org.apache.jackrabbit.test.api.NodeSetPrimaryTypeTest#testSetAbstractAsPrimaryType
-org.apache.jackrabbit.test.api.NodeSetPrimaryTypeTest#testSetCurrentType
-org.apache.jackrabbit.test.api.NodeSetPrimaryTypeTest#testSetCurrentTypeOnNew
-org.apache.jackrabbit.test.api.NodeSetPrimaryTypeTest#testSetMixinAsPrimaryType
-org.apache.jackrabbit.test.api.NodeSetPrimaryTypeTest#testSetPrimaryType
-org.apache.jackrabbit.test.api.nodetype.CanAddChildNodeCallWithNodeTypeTest#testCanAddAbstractType
-org.apache.jackrabbit.test.api.nodetype.CanRemoveItemTest#testMandatoryChildNode
-org.apache.jackrabbit.test.api.nodetype.CanRemoveItemTest#testMandatoryProperty
-org.apache.jackrabbit.test.api.nodetype.CanRemoveItemTest#testProtectedChildNode
-org.apache.jackrabbit.test.api.nodetype.CanRemoveItemTest#testProtectedProperty
-org.apache.jackrabbit.test.api.nodetype.CanRemoveItemTest#testRemovableChildNode
-org.apache.jackrabbit.test.api.nodetype.CanRemoveItemTest#testRemovableProperty
-org.apache.jackrabbit.test.api.nodetype.NodeDefTest#testGetDefaultPrimaryTypes
-org.apache.jackrabbit.test.api.nodetype.NodeDefTest#testGetRequiredPrimaryTypeNames
+
 org.apache.jackrabbit.test.api.nodetype.NodeTypeCreationTest#testEmptyNodeDefinitionTemplate
 org.apache.jackrabbit.test.api.nodetype.NodeTypeCreationTest#testEmptyNodeTypeTemplate
 org.apache.jackrabbit.test.api.nodetype.NodeTypeCreationTest#testEmptyPropertyDefinitionTemplate
@@ -208,39 +170,14 @@ org.apache.jackrabbit.test.api.nodetype.NodeTypeCreationTest#testRegisterNodeTyp
 org.apache.jackrabbit.test.api.nodetype.NodeTypeCreationTest#testRegisterNodeTypes
 org.apache.jackrabbit.test.api.nodetype.NodeTypeCreationTest#testResidualNames
 org.apache.jackrabbit.test.api.nodetype.NodeTypeCreationTest#testSetDefaultValues
-org.apache.jackrabbit.test.api.nodetype.NodeTypeTest#testGetDeclaredSubtypes
-org.apache.jackrabbit.test.api.nodetype.NodeTypeTest#testGetDeclaredSupertypes
-org.apache.jackrabbit.test.api.observation.EventJournalTest#testEventType
-org.apache.jackrabbit.test.api.observation.EventJournalTest#testIsDeepFalse
-org.apache.jackrabbit.test.api.observation.EventJournalTest#testIsDeepTrue
-org.apache.jackrabbit.test.api.observation.EventJournalTest#testLiveJournal
-org.apache.jackrabbit.test.api.observation.EventJournalTest#testNodeType
-org.apache.jackrabbit.test.api.observation.EventJournalTest#testPath
-org.apache.jackrabbit.test.api.observation.EventJournalTest#testSkipTo
-org.apache.jackrabbit.test.api.observation.EventJournalTest#testSkipToNow
-org.apache.jackrabbit.test.api.observation.EventJournalTest#testUserData
-org.apache.jackrabbit.test.api.observation.EventJournalTest#testUUID
-org.apache.jackrabbit.test.api.observation.EventJournalTest#testWorkspaceSeparation
-org.apache.jackrabbit.test.api.observation.GetDateTest#testLinearTime
-org.apache.jackrabbit.test.api.observation.GetIdentifierTest#testNodeAdded
-org.apache.jackrabbit.test.api.observation.GetIdentifierTest#testNodeMoved
-org.apache.jackrabbit.test.api.observation.GetIdentifierTest#testNodeRemoved
-org.apache.jackrabbit.test.api.observation.GetIdentifierTest#testPropertyAdded
-org.apache.jackrabbit.test.api.observation.GetIdentifierTest#testPropertyChanged
-org.apache.jackrabbit.test.api.observation.GetIdentifierTest#testPropertyRemoved
-org.apache.jackrabbit.test.api.observation.GetInfoTest#testNodeAdded
-org.apache.jackrabbit.test.api.observation.GetInfoTest#testNodeRemoved
-org.apache.jackrabbit.test.api.observation.GetInfoTest#testPropertyAdded
+
 org.apache.jackrabbit.test.api.observation.GetUserDataTest#testSave
 org.apache.jackrabbit.test.api.observation.GetUserDataTest#testVersioning
 org.apache.jackrabbit.test.api.observation.GetUserDataTest#testWorkspaceOperation
-org.apache.jackrabbit.test.api.observation.NodeMovedTest#testMoveNode
-org.apache.jackrabbit.test.api.observation.NodeMovedTest#testMoveTree
-org.apache.jackrabbit.test.api.observation.NodeMovedTest#testMoveWithRemove
-org.apache.jackrabbit.test.api.observation.NodeReorderTest#testNodeReorder
-org.apache.jackrabbit.test.api.observation.NodeReorderTest#testNodeReorderSameName
-org.apache.jackrabbit.test.api.observation.NodeReorderTest#testNodeReorderSameNameWithRemove
+org.apache.jackrabbit.test.api.observation.NodeReorderTest#testNodeReorderMove
+
 org.apache.jackrabbit.test.api.PathTest#testResolvedIdentifierBasedPropertyValue
+
 org.apache.jackrabbit.test.api.query.CreateQueryTest#testUnknownQueryLanguage
 org.apache.jackrabbit.test.api.query.DerefQueryLevel1Test#testDerefMultiPropWithNodeStar
 org.apache.jackrabbit.test.api.query.DerefQueryLevel1Test#testDerefMultiPropWithNodeTest
@@ -521,129 +458,22 @@ org.apache.jackrabbit.test.api.query.XPathQueryLevel2Test#testMultiValueSearch
 org.apache.jackrabbit.test.api.query.XPathQueryLevel2Test#testPathColumn
 org.apache.jackrabbit.test.api.query.XPathQueryLevel2Test#testRange
 org.apache.jackrabbit.test.api.query.XPathQueryLevel2Test#testScoreColumn
+
 org.apache.jackrabbit.test.api.security.AccessControlPolicyTest#testSetIllegalPolicy
 org.apache.jackrabbit.test.api.security.RSessionAccessControlPolicyTest#testSetInvalidPolicy
+
 org.apache.jackrabbit.test.api.SerializationTest#testLockExceptionSessionWithHandler
 org.apache.jackrabbit.test.api.SerializationTest#testLockExceptionWorkspaceWithHandler
 org.apache.jackrabbit.test.api.SerializationTest#testVersioningExceptionFileChildSessionContentHandler
 org.apache.jackrabbit.test.api.SerializationTest#testVersioningExceptionFileChildWorkspaceContentHandler
 org.apache.jackrabbit.test.api.SerializationTest#testVersioningExceptionFileParentSessionContentHandler
 org.apache.jackrabbit.test.api.SerializationTest#testVersioningExceptionFileParentWorkspaceContentHandler
+
 org.apache.jackrabbit.test.api.SessionTest#testHasCapability
-org.apache.jackrabbit.test.api.version.ActivitiesTest#testActivitiesPath
-org.apache.jackrabbit.test.api.version.ActivitiesTest#testActivitiesRelation
-org.apache.jackrabbit.test.api.version.ActivitiesTest#testActivitiesRelationWithCheckpoint
-org.apache.jackrabbit.test.api.version.ActivitiesTest#testCreateRemoveActivity
-org.apache.jackrabbit.test.api.version.ActivitiesTest#testSetGetActivity
-org.apache.jackrabbit.test.api.version.ConfigurationsTest#testCreateConfigWithBaseline
-org.apache.jackrabbit.test.api.version.ConfigurationsTest#testRestoreBaseline
+
 org.apache.jackrabbit.test.api.version.CopyTest#testCopy
-org.apache.jackrabbit.test.api.version.GetPredecessorsTest#testGetLinearPredecessorSuccessor
-org.apache.jackrabbit.test.api.version.GetVersionableUUIDTest#testGetVersionableIdentifier
-org.apache.jackrabbit.test.api.version.MergeActivityTest#testMergeActivity
-org.apache.jackrabbit.test.api.version.MergeCancelMergeTest#testMergeNodeCancelMergeJcr2
-org.apache.jackrabbit.test.api.version.MergeDoneMergeTest#testMergeNodeDoneMergeJcr2
-org.apache.jackrabbit.test.api.version.OnParentVersionComputeTest#testRestorePropJcr2
-org.apache.jackrabbit.test.api.version.OnParentVersionCopyTest#testRestoreNodeJcr2
-org.apache.jackrabbit.test.api.version.OnParentVersionIgnoreTest#testRestoreNodeJcr2
-org.apache.jackrabbit.test.api.version.OnParentVersionIgnoreTest#testRestorePropJcr2
-org.apache.jackrabbit.test.api.version.OnParentVersionInitializeTest#testRestorePropJcr2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreChild1Jcr2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreChild1Jcr2_2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreChild1Jcr2_4
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreCorrectPropertyJcr2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreCorrectPropertyJcr2_2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreCorrectPropertyJcr2_4
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreInvalidVersionJcr2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreNameJcr2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOnCheckedInNodeJcr2_1
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOnCheckedInNodeJcr2_2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOnCheckedInNodeJcr2_4
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOnCheckedOutNodeJcr2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOnCheckedOutNodeJcr2_2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOnCheckedOutNodeJcr2_4
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOrder2Jcr2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOrder2Jcr2_2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOrder2Jcr2_3
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOrder2Jcr2_4
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOrderJcr2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOrderJcr2_2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreOrderJcr2_4
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreRemovedJcr2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreRootVersionFailJcr2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreSetsBaseVersionJcr2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreSetsBaseVersionJcr2_2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreSetsBaseVersionJcr2_4
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreSetsIsCheckedOutToFalseJcr2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreSetsIsCheckedOutToFalseJcr2_2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreSetsIsCheckedOutToFalseJcr2_4
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreWithInvalidVersionJcr2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreWithPendingChangesJcr2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreWithPendingChangesJcr2_2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreWithPendingChangesJcr2_4
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreWithUUIDConflictJcr2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreWithUUIDConflictJcr2_2
-org.apache.jackrabbit.test.api.version.RestoreTest#testRestoreWithUUIDConflictJcr2_4
-org.apache.jackrabbit.test.api.version.simple.FrozenNodeTest#testFrozenChildNodeNodeType
-org.apache.jackrabbit.test.api.version.simple.FrozenNodeTest#testFrozenChildNodeType
-org.apache.jackrabbit.test.api.version.simple.FrozenNodeTest#testFrozenChildNodeUUUID
-org.apache.jackrabbit.test.api.version.simple.FrozenNodeTest#testFrozenChildUUUID
-org.apache.jackrabbit.test.api.version.simple.FrozenNodeTest#testFrozenNodeNodeType
-org.apache.jackrabbit.test.api.version.simple.FrozenNodeTest#testFrozenNodeType
-org.apache.jackrabbit.test.api.version.simple.FrozenNodeTest#testFrozenNodeUUUID
-org.apache.jackrabbit.test.api.version.simple.FrozenNodeTest#testFrozenUUUID
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testLinearVersions
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testLinearVersionsJcr2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testLinearVersionsJcr2_2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testLinearVersionsJcr2_3
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testLinearVersionsJcr2_4
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreChild1Jcr2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreChild1Jcr2_2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreChild1Jcr2_4
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreCorrectPropertyJcr2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreCorrectPropertyJcr2_2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreCorrectPropertyJcr2_4
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreInvalidVersionJcr2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreNameJcr2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreOnCheckedInNodeJcr2_1
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreOnCheckedInNodeJcr2_2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreOnCheckedInNodeJcr2_4
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreOnCheckedOutNodeJcr2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreOnCheckedOutNodeJcr2_2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreOnCheckedOutNodeJcr2_4
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreOrder2Jcr2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreOrder2Jcr2_2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreOrder2Jcr2_3
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreOrder2Jcr2_4
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreOrderJcr2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreOrderJcr2_2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreOrderJcr2_4
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreRootVersionFailJcr2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreSetsIsCheckedOutToFalseJcr2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreSetsIsCheckedOutToFalseJcr2_2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreSetsIsCheckedOutToFalseJcr2_4
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreWithInvalidVersionJcr2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreWithPendingChangesJcr2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreWithPendingChangesJcr2_2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreWithPendingChangesJcr2_4
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreWithUUIDConflictJcr2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreWithUUIDConflictJcr2_2
-org.apache.jackrabbit.test.api.version.simple.RestoreTest#testRestoreWithUUIDConflictJcr2_4
 org.apache.jackrabbit.test.api.version.VersionHistoryTest#testGetAllFrozenNodes
-org.apache.jackrabbit.test.api.version.VersionHistoryTest#testInitiallyGetAllLinearVersionsContainsTheRootAndTheBaseVersion
-org.apache.jackrabbit.test.api.version.VersionHistoryTest#testInitialNumberOfLinearVersions
-org.apache.jackrabbit.test.api.version.WorkspaceRestoreTest#testWorkspaceRestoreHasCorrespondingNode
-org.apache.jackrabbit.test.api.version.WorkspaceRestoreTest#testWorkspaceRestoreHasCorrespondingNodeJcr2
-org.apache.jackrabbit.test.api.version.WorkspaceRestoreTest#testWorkspaceRestoreOnCheckedInNode
-org.apache.jackrabbit.test.api.version.WorkspaceRestoreTest#testWorkspaceRestoreOnCheckedInNodeJcr2
-org.apache.jackrabbit.test.api.version.WorkspaceRestoreTest#testWorkspaceRestoreOnCheckedOutNode
-org.apache.jackrabbit.test.api.version.WorkspaceRestoreTest#testWorkspaceRestoreOnCheckedOutNodeJcr2
-org.apache.jackrabbit.test.api.version.WorkspaceRestoreTest#testWorkspaceRestoreWithPendingChanges
-org.apache.jackrabbit.test.api.version.WorkspaceRestoreTest#testWorkspaceRestoreWithPendingChangesJcr2
-org.apache.jackrabbit.test.api.version.WorkspaceRestoreTest#testWorkspaceRestoreWithRemoveExisting
-org.apache.jackrabbit.test.api.version.WorkspaceRestoreTest#testWorkspaceRestoreWithRemoveExistingJcr2
-org.apache.jackrabbit.test.api.version.WorkspaceRestoreTest#testWorkspaceRestoreWithUUIDConflict
-org.apache.jackrabbit.test.api.version.WorkspaceRestoreTest#testWorkspaceRestoreWithUUIDConflictJcr2
+
 org.apache.jackrabbit.test.api.security.AccessControlPolicyTest#testSetPolicy
 org.apache.jackrabbit.test.api.security.AccessControlPolicyTest#testSetAllPolicies
 org.apache.jackrabbit.test.api.security.AccessControlPolicyTest#testGetPolicyAfterSet
@@ -674,7 +504,7 @@ org.apache.jackrabbit.test.api.security.AccessControlListTest#testAddAccessContr
 org.apache.jackrabbit.test.api.security.AccessControlListTest#testAddAccessControlEntryAgain
 org.apache.jackrabbit.test.api.security.AccessControlListTest#testExtendPrivileges
 org.apache.jackrabbit.test.api.security.RSessionAccessControlPolicyTest#testSetPolicy
-<!-- JCRRMI-26 -->
+<!-- JCR-3206 -->
 org.apache.jackrabbit.test.api.observation.EventJournalTest
                   </value>
                 </property>
@@ -719,7 +549,7 @@ org.apache.jackrabbit.test.api.observation.EventJournalTest
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-commons</artifactId>
-        <version>2.3.6</version>
+        <version>${project.version}</version>
       <scope>provided</scope>
     </dependency>
     
@@ -727,7 +557,7 @@ org.apache.jackrabbit.test.api.observation.EventJournalTest
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-api</artifactId>
-        <version>2.3.6</version>
+        <version>${project.version}</version>
       <scope>provided</scope>
     </dependency>
 
@@ -740,13 +570,13 @@ org.apache.jackrabbit.test.api.observation.EventJournalTest
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-core</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-tests</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
     <dependency>
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/BrokenRemoteRepository.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/BrokenRemoteRepository.java
index 306fb1b..9201f1a 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/BrokenRemoteRepository.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/BrokenRemoteRepository.java
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.rmi.client;
 import java.rmi.RemoteException;
 
 import javax.jcr.Credentials;
+import javax.jcr.Value;
 
 import org.apache.jackrabbit.rmi.remote.RemoteRepository;
 import org.apache.jackrabbit.rmi.remote.RemoteSession;
@@ -130,4 +131,48 @@ public class BrokenRemoteRepository implements RemoteRepository {
         throw exception;
     }
 
+    /**
+     * Throws a {@link RemoteException}.
+     *
+     * @param key ignored
+     * @return nothing
+     * @throws RemoteException always thrown
+     */
+	public Value getDescriptorValue(String key) throws RemoteException {
+        throw exception;
+	}
+
+    /**
+     * Throws a {@link RemoteException}.
+     *
+     * @param key ignored
+     * @return nothing
+     * @throws RemoteException always thrown
+     */
+	public Value[] getDescriptorValues(String key) throws RemoteException {
+        throw exception;
+	}
+
+    /**
+     * Throws a {@link RemoteException}.
+     *
+     * @param key ignored
+     * @return nothing
+     * @throws RemoteException always thrown
+     */
+	public boolean isSingleValueDescriptor(String key) throws RemoteException {
+        throw exception;
+	}
+
+    /**
+     * Throws a {@link RemoteException}.
+     *
+     * @param key ignored
+     * @return nothing
+     * @throws RemoteException always thrown
+     */
+	public boolean isStandardDescriptor(String key) throws RemoteException {
+        throw exception;
+	}
+
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientAdapterFactory.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientAdapterFactory.java
index 65fa386..14959c9 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientAdapterFactory.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientAdapterFactory.java
@@ -30,11 +30,11 @@ import javax.jcr.Session;
 import javax.jcr.Workspace;
 import javax.jcr.lock.Lock;
 import javax.jcr.lock.LockManager;
+import javax.jcr.nodetype.ItemDefinition;
+import javax.jcr.nodetype.NodeDefinition;
 import javax.jcr.nodetype.NodeType;
 import javax.jcr.nodetype.NodeTypeIterator;
 import javax.jcr.nodetype.NodeTypeManager;
-import javax.jcr.nodetype.ItemDefinition;
-import javax.jcr.nodetype.NodeDefinition;
 import javax.jcr.nodetype.PropertyDefinition;
 import javax.jcr.observation.ObservationManager;
 import javax.jcr.query.Query;
@@ -301,8 +301,8 @@ public class ClientAdapterFactory implements LocalAdapterFactory {
      *
      * {@inheritDoc}
      */
-    public Row getRow(RemoteRow remote) {
-        return new ClientRow(remote);
+    public Row getRow(Session session, RemoteRow remote) {
+        return new ClientRow(session, remote, this);
     }
 
     /**
@@ -344,8 +344,8 @@ public class ClientAdapterFactory implements LocalAdapterFactory {
      * Creates and returns a {@link ClientRowIterator} instance.
      * {@inheritDoc}
      */
-    public RowIterator getRowIterator(RemoteIterator remote) {
-        return new ClientRowIterator(remote, this);
+    public RowIterator getRowIterator(Session session, RemoteIterator remote) {
+        return new ClientRowIterator(session, remote, this);
     }
 
     public LockManager getLockManager(
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientLock.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientLock.java
index d345fc9..c4abc32 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientLock.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientLock.java
@@ -21,7 +21,6 @@ import java.rmi.RemoteException;
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
-import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.lock.Lock;
 
 import org.apache.jackrabbit.rmi.remote.RemoteLock;
@@ -121,11 +120,21 @@ public class ClientLock extends ClientObject implements Lock {
         }
     }
 
+    /** {@inheritDoc} */
     public long getSecondsRemaining() throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            return remote.getSecondsRemaining();
+        } catch (RemoteException ex) {
+            throw new RemoteRuntimeException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public boolean isLockOwningSession() {
-        throw new RuntimeException("TODO: JCRRMI-26");
+        try {
+            return remote.isLockOwningSession();
+        } catch (RemoteException ex) {
+            throw new RemoteRuntimeException(ex);
+        }
     }
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientNode.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientNode.java
index dd48c37..09f2f2b 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientNode.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientNode.java
@@ -30,7 +30,6 @@ import javax.jcr.Property;
 import javax.jcr.PropertyIterator;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
-import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.Value;
 import javax.jcr.lock.Lock;
 import javax.jcr.nodetype.NodeDefinition;
@@ -720,40 +719,80 @@ public class ClientNode extends ClientItem implements Node {
         }
     }
 
+    /** {@inheritDoc} */
     public void followLifecycleTransition(String transition)
             throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+        	remote.followLifecycleTransition(transition);
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public String[] getAllowedLifecycleTransistions()
             throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+        	return remote.getAllowedLifecycleTransistions();
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public NodeIterator getSharedSet() throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            return getFactory().getNodeIterator(getSession(), remote.getSharedSet());
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public PropertyIterator getWeakReferences() throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            return getFactory().getPropertyIterator(getSession(), remote.getWeakReferences());
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public PropertyIterator getWeakReferences(String name)
             throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            return getFactory().getPropertyIterator(getSession(), remote.getWeakReferences(name));
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public void removeShare() throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            remote.removeShare();
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public void removeSharedSet() throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            remote.removeSharedSet();
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public void setPrimaryType(String nodeTypeName)
             throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            remote.setPrimaryType(nodeTypeName);
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientNodeDefinition.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientNodeDefinition.java
index a9759bd..1412f22 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientNodeDefinition.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientNodeDefinition.java
@@ -81,12 +81,22 @@ public class ClientNodeDefinition extends ClientItemDefinition implements NodeDe
         }
     }
 
+    /** {@inheritDoc} */
     public String getDefaultPrimaryTypeName() {
-        throw new RuntimeException("TODO: JCRRMI-26");
+        try {
+            return remote.getDefaultPrimaryTypeName();
+        } catch (RemoteException ex) {
+            throw new RemoteRuntimeException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public String[] getRequiredPrimaryTypeNames() {
-        throw new RuntimeException("TODO: JCRRMI-26");
+        try {
+            return remote.getRequiredPrimaryTypeNames();
+        } catch (RemoteException ex) {
+            throw new RemoteRuntimeException(ex);
+        }
     }
 
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientNodeType.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientNodeType.java
index 723ff38..5f23c04 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientNodeType.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientNodeType.java
@@ -250,26 +250,52 @@ public class ClientNodeType extends ClientObject implements NodeType {
         }
     }
 
+    /** {@inheritDoc} */
     public boolean canRemoveNode(String nodeName) {
-        throw new RuntimeException("TODO: JCRRMI-26");
+        try {
+            return remote.canRemoveNode(nodeName);
+        } catch (RemoteException ex) {
+            throw new RemoteRuntimeException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public boolean canRemoveProperty(String propertyName) {
-        throw new RuntimeException("TODO: JCRRMI-26");
+        try {
+            return remote.canRemoveProperty(propertyName);
+        } catch (RemoteException ex) {
+            throw new RemoteRuntimeException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public NodeTypeIterator getDeclaredSubtypes() {
-        throw new RuntimeException("TODO: JCRRMI-26");
+        try {
+            return getFactory().getNodeTypeIterator(remote.getDeclaredSubtypes());
+        } catch (RemoteException ex) {
+            throw new RemoteRuntimeException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public NodeTypeIterator getSubtypes() {
-        throw new RuntimeException("TODO: JCRRMI-26");
+        try {
+            return getFactory().getNodeTypeIterator(remote.getSubtypes());
+        } catch (RemoteException ex) {
+            throw new RemoteRuntimeException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public String[] getDeclaredSupertypeNames() {
-        throw new RuntimeException("TODO: JCRRMI-26");
+        try {
+            return remote.getDeclaredSupertypeNames();
+        } catch (RemoteException ex) {
+            throw new RemoteRuntimeException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public boolean isAbstract() {
         try {
             return remote.isAbstract();
@@ -278,7 +304,12 @@ public class ClientNodeType extends ClientObject implements NodeType {
         }
     }
 
+    /** {@inheritDoc} */
     public boolean isQueryable() {
-        throw new RuntimeException("TODO: JCRRMI-26");
+        try {
+            return remote.isQueryable();
+        } catch (RemoteException ex) {
+            throw new RemoteRuntimeException(ex);
+        }
     }
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientNodeTypeManager.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientNodeTypeManager.java
index f1522ee..c1d9e43 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientNodeTypeManager.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientNodeTypeManager.java
@@ -96,22 +96,22 @@ public class ClientNodeTypeManager extends ClientObject
 
     public NodeDefinitionTemplate createNodeDefinitionTemplate()
             throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        throw new UnsupportedRepositoryOperationException("TODO: JCR-3206");
     }
 
     public NodeTypeTemplate createNodeTypeTemplate()
             throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        throw new UnsupportedRepositoryOperationException("TODO: JCR-3206");
     }
 
     public NodeTypeTemplate createNodeTypeTemplate(NodeTypeDefinition ntd)
             throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        throw new UnsupportedRepositoryOperationException("TODO: JCR-3206");
     }
 
     public PropertyDefinitionTemplate createPropertyDefinitionTemplate()
             throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        throw new UnsupportedRepositoryOperationException("TODO: JCR-3206");
     }
 
     public boolean hasNodeType(String name) throws RepositoryException {
@@ -125,13 +125,13 @@ public class ClientNodeTypeManager extends ClientObject
     public NodeType registerNodeType(
             NodeTypeDefinition ntd, boolean allowUpdate)
             throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        throw new UnsupportedRepositoryOperationException("TODO: JCR-3206");
     }
 
     public NodeTypeIterator registerNodeTypes(
             NodeTypeDefinition[] ntds, boolean allowUpdate)
             throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        throw new UnsupportedRepositoryOperationException("TODO: JCR-3206");
     }
 
     public void unregisterNodeType(String name) throws RepositoryException {
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientObservationManager.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientObservationManager.java
index 722d810..d1b5700 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientObservationManager.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientObservationManager.java
@@ -109,17 +109,17 @@ public class ClientObservationManager extends ClientObject implements
     }
 
     public EventJournal getEventJournal() throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        throw new UnsupportedRepositoryOperationException("TODO: JCR-3206");
     }
 
     public EventJournal getEventJournal(
             int eventTypes, String absPath, boolean isDeep,
             String[] uuid, String[] nodeTypeName) throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        throw new UnsupportedRepositoryOperationException("TODO: JCR-3206");
     }
 
     public void setUserData(String userData) throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        throw new UnsupportedRepositoryOperationException("TODO: JCR-3206");
     }
 
     //---------- internal ------------------------------------------------------
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientPropertyDefinition.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientPropertyDefinition.java
index 6d2d575..15955b3 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientPropertyDefinition.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientPropertyDefinition.java
@@ -85,16 +85,31 @@ public class ClientPropertyDefinition extends ClientItemDefinition implements Pr
         }
     }
 
+    /** {@inheritDoc} */
     public String[] getAvailableQueryOperators() {
-        throw new RuntimeException("TODO: JCRRMI-26");
+        try {
+            return remote.getAvailableQueryOperators();
+        } catch (RemoteException ex) {
+            throw new RemoteRuntimeException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public boolean isFullTextSearchable() {
-        throw new RuntimeException("TODO: JCRRMI-26");
+        try {
+            return remote.isFullTextSearchable();
+        } catch (RemoteException ex) {
+            throw new RemoteRuntimeException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public boolean isQueryOrderable() {
-        throw new RuntimeException("TODO: JCRRMI-26");
+        try {
+            return remote.isQueryOrderable();
+        } catch (RemoteException ex) {
+            throw new RemoteRuntimeException(ex);
+        }
     }
 
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientQuery.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientQuery.java
index 39b62c6..0de4fde 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientQuery.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientQuery.java
@@ -21,7 +21,6 @@ import java.rmi.RemoteException;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.Node;
-import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.Value;
 import javax.jcr.query.Query;
 import javax.jcr.query.QueryResult;
@@ -104,21 +103,41 @@ public class ClientQuery extends ClientObject implements Query {
         }
     }
 
+    /** {@inheritDoc} */
     public void bindValue(String varName, Value value)
             throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            remote.bindValue(varName, value);
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public String[] getBindVariableNames() throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            return remote.getBindVariableNames();
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public void setLimit(long limit) {
-        throw new RuntimeException("TODO: JCRRMI-26");
+        try {
+            remote.setLimit(limit);
+        } catch (RemoteException ex) {
+            throw new RemoteRuntimeException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public void setOffset(long offset) {
-        throw new RuntimeException("TODO: JCRRMI-26");
+        try {
+            remote.setOffset(offset);
+        } catch (RemoteException ex) {
+            throw new RemoteRuntimeException(ex);
+        }
     }
 
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientQueryManager.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientQueryManager.java
index bd38022..a691999 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientQueryManager.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientQueryManager.java
@@ -91,7 +91,7 @@ public class ClientQueryManager extends ClientObject implements QueryManager {
     }
 
     public QueryObjectModelFactory getQOMFactory() {
-        throw new RuntimeException("TODO: JCRRMI-26");
+        throw new RuntimeException("TODO: JCR-3206");
     }
 
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientQueryResult.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientQueryResult.java
index d23eb80..dd2ef4b 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientQueryResult.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientQueryResult.java
@@ -21,7 +21,6 @@ import java.rmi.RemoteException;
 import javax.jcr.NodeIterator;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
-import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.query.QueryResult;
 import javax.jcr.query.RowIterator;
 
@@ -71,7 +70,7 @@ public class ClientQueryResult extends ClientObject implements QueryResult {
     /** {@inheritDoc} */
     public RowIterator getRows() throws RepositoryException {
         try {
-            return getFactory().getRowIterator(remote.getRows());
+            return getFactory().getRowIterator(session, remote.getRows());
         } catch (RemoteException ex) {
             throw new RemoteRepositoryException(ex);
         }
@@ -86,8 +85,13 @@ public class ClientQueryResult extends ClientObject implements QueryResult {
         }
     }
 
+    /** {@inheritDoc} */
     public String[] getSelectorNames() throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            return remote.getSelectorNames();
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientRepository.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientRepository.java
index a3c3ba0..f292298 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientRepository.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientRepository.java
@@ -136,11 +136,10 @@ public class ClientRepository implements Repository {
 
     /** {@inheritDoc} */
     public Value[] getDescriptorValues(String key) {
-        Value value = getDescriptorValue(key);
-        if (value != null) {
-            return new Value[] { value };
-        } else {
-            return null;
+        try {
+            return remote.getDescriptorValues(key);
+        } catch (RemoteException ex) {
+            throw new RemoteRuntimeException(ex);
         }
     }
 
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientRow.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientRow.java
index b40af26..253b3c4 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientRow.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientRow.java
@@ -20,7 +20,7 @@ import java.rmi.RemoteException;
 
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
-import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Session;
 import javax.jcr.Value;
 import javax.jcr.query.Row;
 
@@ -34,7 +34,10 @@ import org.apache.jackrabbit.rmi.remote.RemoteRow;
  * @see javax.jcr.query.Row Row
  * @see org.apache.jackrabbit.rmi.remote.RemoteRow
  */
-public class ClientRow implements Row {
+public class ClientRow extends ClientObject implements Row {
+
+    /** Current session. */
+    private Session session;
 
     /** The remote query row. */
     private RemoteRow remote;
@@ -44,7 +47,10 @@ public class ClientRow implements Row {
      *
      * @param remote remote query row
      */
-    public ClientRow(RemoteRow remote) {
+    public ClientRow(Session session, RemoteRow remote,
+            LocalAdapterFactory factory) {
+        super(factory);
+        this.session = session;
         this.remote = remote;
     }
 
@@ -66,28 +72,58 @@ public class ClientRow implements Row {
         }
     }
 
+    /** {@inheritDoc} */
     public Node getNode() throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            return getFactory().getNode(session, remote.getNode());
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public Node getNode(String selectorName) throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            return getFactory().getNode(session, remote.getNode(selectorName));
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public String getPath() throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            return remote.getPath();
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public String getPath(String selectorName) throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            return remote.getPath(selectorName);
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public double getScore() throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            return remote.getScore();
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public double getScore(String selectorName) throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            return remote.getScore(selectorName);
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientSession.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientSession.java
index d0255a4..909ad2a 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientSession.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientSession.java
@@ -575,13 +575,13 @@ public class ClientSession extends ClientObject implements Session {
 
     public RetentionManager getRetentionManager()
             throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        throw new UnsupportedRepositoryOperationException("TODO: JCR-3206");
     }
 
     public boolean hasCapability(
             String methodName, Object target, Object[] arguments)
             throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        throw new UnsupportedRepositoryOperationException("TODO: JCR-3206");
     }
 
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientVersion.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientVersion.java
index 91df116..edce561 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientVersion.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientVersion.java
@@ -22,7 +22,6 @@ import java.util.Calendar;
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
-import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.version.Version;
 import javax.jcr.version.VersionHistory;
 
@@ -114,15 +113,40 @@ public class ClientVersion extends ClientNode implements Version {
         }
    }
 
+    /** {@inheritDoc} */
     public Node getFrozenNode() throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            return getFactory().getNode(getSession(), remote.getFrozenNode());
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public Version getLinearPredecessor() throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            RemoteVersion linearPredecessor = remote.getLinearPredecessor();
+            if (linearPredecessor == null) {
+                return null;
+            } else {
+                return getFactory().getVersion(getSession(), linearPredecessor);
+            }
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public Version getLinearSuccessor() throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            RemoteVersion linearSuccessor = remote.getLinearSuccessor();
+            if (linearSuccessor == null) {
+                return null;
+            } else {
+                return getFactory().getVersion(getSession(), linearSuccessor);
+            }
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientVersionHistory.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientVersionHistory.java
index 1b03917..5d855cf 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientVersionHistory.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientVersionHistory.java
@@ -128,8 +128,8 @@ public class ClientVersionHistory extends ClientNode implements VersionHistory {
     public boolean hasVersionLabel(Version version, String label)
             throws VersionException, RepositoryException {
         try {
-            String versionUUID = version.getUUID();
-            return remote.hasVersionLabel(versionUUID, label);
+            String versionIdentifier = version.getIdentifier();
+            return remote.hasVersionLabel(versionIdentifier, label);
         } catch (RemoteException ex) {
             throw new RemoteRepositoryException(ex);
         }
@@ -149,8 +149,8 @@ public class ClientVersionHistory extends ClientNode implements VersionHistory {
     public String[] getVersionLabels(Version version)
             throws VersionException, RepositoryException {
         try {
-            String versionUUID = version.getUUID();
-            return remote.getVersionLabels(versionUUID);
+            String versionIdentifier = version.getIdentifier();
+            return remote.getVersionLabels(versionIdentifier);
         } catch (RemoteException ex) {
             throw new RemoteRepositoryException(ex);
         }
@@ -167,7 +167,10 @@ public class ClientVersionHistory extends ClientNode implements VersionHistory {
         }
     }
 
-    /** {@inheritDoc} */
+    /** {@inheritDoc}
+     * @deprecated As of JCR 2.0, {@link #getVersionableIdentifier} should be
+     *             used instead.
+     */
     public String getVersionableUUID() throws RepositoryException {
         try {
             return remote.getVersionableUUID();
@@ -176,19 +179,39 @@ public class ClientVersionHistory extends ClientNode implements VersionHistory {
         }
     }
 
+    /** {@inheritDoc} */
     public NodeIterator getAllFrozenNodes() throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            return getFactory().getNodeIterator(getSession(), remote.getAllFrozenNodes());
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public NodeIterator getAllLinearFrozenNodes() throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            return getFactory().getNodeIterator(getSession(), remote.getAllLinearFrozenNodes());
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public VersionIterator getAllLinearVersions() throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            return getFactory().getVersionIterator(getSession(), remote.getAllLinearVersions());
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 
+    /** {@inheritDoc} */
     public String getVersionableIdentifier() throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            return remote.getVersionableIdentifier();
+        } catch (RemoteException ex) {
+            throw new RemoteRepositoryException(ex);
+        }
     }
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientVersionManager.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientVersionManager.java
index e490f47..07f9ded 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientVersionManager.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientVersionManager.java
@@ -22,11 +22,12 @@ import javax.jcr.Node;
 import javax.jcr.NodeIterator;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
-import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.version.Version;
 import javax.jcr.version.VersionHistory;
 import javax.jcr.version.VersionManager;
 
+import org.apache.jackrabbit.rmi.remote.RemoteIterator;
+import org.apache.jackrabbit.rmi.remote.RemoteNode;
 import org.apache.jackrabbit.rmi.remote.RemoteVersionManager;
 
 public class ClientVersionManager extends ClientObject
@@ -45,11 +46,17 @@ public class ClientVersionManager extends ClientObject
         this.remote = remote;
     }
 
+    /** {@inheritDoc} */
     public void cancelMerge(String absPath, Version version)
             throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            remote.cancelMerge(absPath, version.getIdentifier());
+        } catch (RemoteException e) {
+            throw new RemoteRepositoryException(e);
+        }
     }
 
+    /** {@inheritDoc} */
     public Version checkin(String absPath) throws RepositoryException {
         try {
             return getFactory().getVersion(session, remote.checkin(absPath));
@@ -58,6 +65,7 @@ public class ClientVersionManager extends ClientObject
         }
     }
 
+    /** {@inheritDoc} */
     public void checkout(String absPath) throws RepositoryException {
         try {
             remote.checkout(absPath);
@@ -66,6 +74,7 @@ public class ClientVersionManager extends ClientObject
         }
     }
 
+    /** {@inheritDoc} */
     public Version checkpoint(String absPath) throws RepositoryException {
         try {
             return getFactory().getVersion(session, remote.checkpoint(absPath));
@@ -74,6 +83,7 @@ public class ClientVersionManager extends ClientObject
         }
     }
 
+    /** {@inheritDoc} */
     public Node createActivity(String title)
             throws RepositoryException {
         try {
@@ -83,6 +93,7 @@ public class ClientVersionManager extends ClientObject
         }
     }
 
+    /** {@inheritDoc} */
     public Node createConfiguration(String absPath)
             throws RepositoryException {
         try {
@@ -93,19 +104,31 @@ public class ClientVersionManager extends ClientObject
         }
     }
 
+    /** {@inheritDoc} */
     public void doneMerge(String absPath, Version version)
             throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            remote.doneMerge(absPath, version.getIdentifier());
+        } catch (RemoteException e) {
+            throw new RemoteRepositoryException(e);
+        }
     }
 
+    /** {@inheritDoc} */
     public Node getActivity() throws RepositoryException {
         try {
-            return getFactory().getNode(session, remote.getActivity());
+            RemoteNode activity = remote.getActivity();
+            if (activity == null) {
+                return null;
+            } else {
+                return getFactory().getNode(session, activity);
+            }
         } catch (RemoteException e) {
             throw new RemoteRepositoryException(e);
         }
     }
 
+    /** {@inheritDoc} */
     public Version getBaseVersion(String absPath) throws RepositoryException {
         try {
             return getFactory().getVersion(
@@ -115,6 +138,7 @@ public class ClientVersionManager extends ClientObject
         }
     }
 
+    /** {@inheritDoc} */
     public VersionHistory getVersionHistory(String absPath)
             throws RepositoryException {
         try {
@@ -125,6 +149,7 @@ public class ClientVersionManager extends ClientObject
         }
     }
 
+    /** {@inheritDoc} */
     public boolean isCheckedOut(String absPath) throws RepositoryException {
         try {
             return remote.isCheckedOut(absPath);
@@ -133,10 +158,17 @@ public class ClientVersionManager extends ClientObject
         }
     }
 
+    /** {@inheritDoc} */
     public NodeIterator merge(Node activityNode) throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            RemoteIterator iterator = remote.merge(activityNode.getIdentifier());
+            return getFactory().getNodeIterator(session, iterator);
+        } catch (RemoteException e) {
+            throw new RemoteRepositoryException(e);
+        }
     }
 
+    /** {@inheritDoc} */
     public NodeIterator merge(
             String absPath, String srcWorkspace, boolean bestEffort)
             throws RepositoryException {
@@ -148,6 +180,7 @@ public class ClientVersionManager extends ClientObject
         }
     }
 
+    /** {@inheritDoc} */
     public NodeIterator merge(
             String absPath, String srcWorkspace, boolean bestEffort,
             boolean isShallow) throws RepositoryException {
@@ -159,20 +192,40 @@ public class ClientVersionManager extends ClientObject
         }
     }
 
+    /** {@inheritDoc} */
     public void removeActivity(Node activityNode) throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            remote.removeActivity(activityNode.getIdentifier());
+        } catch (RemoteException e) {
+            throw new RemoteRepositoryException(e);
+        }
     }
 
+    /** {@inheritDoc} */
     public void restore(Version[] versions, boolean removeExisting)
             throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            String[] versionIdentifiers = new String[versions.length];
+            for (int i = 0; i < versions.length; i++) {
+                versionIdentifiers[i] = versions[i].getIdentifier();
+            }
+            remote.restore(versionIdentifiers, removeExisting);
+        } catch (RemoteException e) {
+            throw new RemoteRepositoryException(e);
+        }
     }
 
+    /** {@inheritDoc} */
     public void restore(Version version, boolean removeExisting)
             throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            remote.restore(version.getIdentifier(), removeExisting);
+        } catch (RemoteException e) {
+            throw new RemoteRepositoryException(e);
+        }
     }
 
+    /** {@inheritDoc} */
     public void restore(
             String absPath, String versionName, boolean removeExisting)
             throws RepositoryException {
@@ -183,11 +236,17 @@ public class ClientVersionManager extends ClientObject
         }
     }
 
+    /** {@inheritDoc} */
     public void restore(String absPath, Version version, boolean removeExisting)
             throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            remote.restoreVI(absPath, version.getIdentifier(), removeExisting);
+        } catch (RemoteException e) {
+            throw new RemoteRepositoryException(e);
+        }
     }
 
+    /** {@inheritDoc} */
     public void restoreByLabel(
             String absPath, String versionLabel, boolean removeExisting)
             throws RepositoryException {
@@ -198,8 +257,23 @@ public class ClientVersionManager extends ClientObject
         }
     }
 
+    /** {@inheritDoc} */
     public Node setActivity(Node activity) throws RepositoryException {
-        throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+        try {
+            RemoteNode remoteActivity;
+            if (activity == null) {
+                remoteActivity = remote.setActivity(null);
+            } else {
+                remoteActivity = remote.setActivity(activity.getIdentifier());
+            }
+            if (remoteActivity == null) {
+                return null;
+            } else {
+                return getFactory().getNode(session, remoteActivity);
+            }
+        } catch (RemoteException e) {
+            throw new RemoteRepositoryException(e);
+        }
     }
 
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientWorkspace.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientWorkspace.java
index db7572a..89f83f4 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientWorkspace.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/ClientWorkspace.java
@@ -24,7 +24,6 @@ import java.rmi.RemoteException;
 import javax.jcr.NamespaceRegistry;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
-import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.Workspace;
 import javax.jcr.lock.LockManager;
 import javax.jcr.nodetype.NodeTypeManager;
@@ -240,8 +239,7 @@ public class ClientWorkspace extends ClientObject implements Workspace {
     /** {@inheritDoc} */
     public void restore(Version[] versions, boolean removeExisting)
             throws RepositoryException {
-        // TODO Auto-generated method stub
-        throw new UnsupportedRepositoryOperationException();
+        getVersionManager().restore(versions, removeExisting);
     }
 
     public void createWorkspace(String name) throws RepositoryException {
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/LocalAdapterFactory.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/LocalAdapterFactory.java
index ce585d4..4769171 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/LocalAdapterFactory.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/LocalAdapterFactory.java
@@ -285,10 +285,11 @@ public interface LocalAdapterFactory {
     /**
      * Factory method for creating a local adapter for a remote query row.
      *
+     * @param session current session
      * @param remote remote query row
      * @return local query row adapter
      */
-    Row getRow(RemoteRow remote);
+    Row getRow(Session session, RemoteRow remote);
 
     /**
      * Factory method for creating a local adapter for a remote node iterator.
@@ -329,10 +330,11 @@ public interface LocalAdapterFactory {
     /**
      * Factory method for creating a local adapter for a remote row iterator.
      *
+     * @param session current session
      * @param remote remote row iterator
      * @return local row iterator adapter
      */
-    RowIterator getRowIterator(RemoteIterator remote);
+    RowIterator getRowIterator(Session session, RemoteIterator remote);
 
     LockManager getLockManager(Session session, RemoteLockManager lockManager);
 
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/SafeClientRepository.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/SafeClientRepository.java
index f5903b1..4b6acaf 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/SafeClientRepository.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/SafeClientRepository.java
@@ -48,13 +48,12 @@ public abstract class SafeClientRepository extends ClientObject
     /**
      * Creates a client adapter for the given remote repository.
      *
-     * @param remote remote repository
      * @param factory local adapter factory
      */
     public SafeClientRepository(LocalAdapterFactory factory) {
         super(factory);
         try {
-            remote = getRemoteRepository();
+            remote = getRemoteRepository(true);
         } catch (RemoteException e) {
             remote = new BrokenRemoteRepository(e);
         }
@@ -69,13 +68,34 @@ public abstract class SafeClientRepository extends ClientObject
     protected abstract RemoteRepository getRemoteRepository()
             throws RemoteException;
 
+    /**
+     * Method to obtain the remote remote repository.
+     * If initialize is true and a RepositoryException will be thrown no {@link BrokenRemoteRepository}
+     * will be created. 
+     *
+     * @return remote repository
+     * @throws RemoteException if the remote repository could not be accessed
+     */
+    protected RemoteRepository getRemoteRepository(boolean initialize)
+            throws RemoteException {
+        if (initialize) {
+            try {
+                return getRemoteRepository();
+            } catch (RemoteException e) {
+                throw new RemoteRuntimeException(e);
+            }
+        } else {
+            return getRemoteRepository();
+        }
+    }
+
     /** {@inheritDoc} */
     public synchronized String getDescriptor(String name) {
         try {
             return remote.getDescriptor(name);
         } catch (RemoteException e1) {
             try {
-                remote = getRemoteRepository();
+                remote = getRemoteRepository(false);
                 return remote.getDescriptor(name);
             } catch (RemoteException e2) {
                 remote = new BrokenRemoteRepository(e2);
@@ -90,7 +110,7 @@ public abstract class SafeClientRepository extends ClientObject
             return remote.getDescriptorKeys();
         } catch (RemoteException e1) {
             try {
-                remote = getRemoteRepository();
+                remote = getRemoteRepository(false);
                 return remote.getDescriptorKeys();
             } catch (RemoteException e2) {
                 remote = new BrokenRemoteRepository(e2);
@@ -106,7 +126,7 @@ public abstract class SafeClientRepository extends ClientObject
             return remote.login(credentials, workspace);
         } catch (RemoteException e1) {
             try {
-                remote = getRemoteRepository();
+                remote = getRemoteRepository(false);
                 return remote.login(credentials, workspace);
             } catch (RemoteException e2) {
                 remote = new BrokenRemoteRepository(e2);
@@ -137,20 +157,64 @@ public abstract class SafeClientRepository extends ClientObject
         return login(null, null);
     }
 
-    public Value getDescriptorValue(String key) {
-        throw new RuntimeException("TODO: JCRRMI-26");
+    /** {@inheritDoc} */
+    public synchronized Value getDescriptorValue(String key) {
+        try {
+            return remote.getDescriptorValue(key);
+        } catch (RemoteException e1) {
+            try {
+                remote = getRemoteRepository(false);
+                return remote.getDescriptorValue(key);
+            } catch (RemoteException e2) {
+                remote = new BrokenRemoteRepository(e2);
+                throw new RemoteRuntimeException(e2);
+            }
+        }
     }
 
-    public Value[] getDescriptorValues(String key) {
-        throw new RuntimeException("TODO: JCRRMI-26");
+    /** {@inheritDoc} */
+    public synchronized Value[] getDescriptorValues(String key) {
+        try {
+            return remote.getDescriptorValues(key);
+        } catch (RemoteException e1) {
+            try {
+                remote = getRemoteRepository(false);
+                return remote.getDescriptorValues(key);
+            } catch (RemoteException e2) {
+                remote = new BrokenRemoteRepository(e2);
+                throw new RemoteRuntimeException(e2);
+            }
+        }
     }
 
-    public boolean isSingleValueDescriptor(String key) {
-        throw new RuntimeException("TODO: JCRRMI-26");
+    /** {@inheritDoc} */
+    public synchronized boolean isSingleValueDescriptor(String key) {
+        try {
+            return remote.isSingleValueDescriptor(key);
+        } catch (RemoteException e1) {
+            try {
+                remote = getRemoteRepository(false);
+                return remote.isSingleValueDescriptor(key);
+            } catch (RemoteException e2) {
+                remote = new BrokenRemoteRepository(e2);
+                throw new RemoteRuntimeException(e2);
+            }
+        }
     }
 
-    public boolean isStandardDescriptor(String key) {
-        throw new RuntimeException("TODO: JCRRMI-26");
+    /** {@inheritDoc} */
+    public synchronized boolean isStandardDescriptor(String key) {
+        try {
+            return remote.isStandardDescriptor(key);
+        } catch (RemoteException e1) {
+            try {
+                remote = getRemoteRepository(false);
+                return remote.isStandardDescriptor(key);
+            } catch (RemoteException e2) {
+                remote = new BrokenRemoteRepository(e2);
+                throw new RemoteRuntimeException(e2);
+            }
+        }
     }
 
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/iterator/ClientRowIterator.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/iterator/ClientRowIterator.java
index 1e85dd0..7014fbe 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/iterator/ClientRowIterator.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/iterator/ClientRowIterator.java
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.rmi.client.iterator;
 
+import javax.jcr.Session;
 import javax.jcr.query.Row;
 import javax.jcr.query.RowIterator;
 
@@ -28,15 +29,19 @@ import org.apache.jackrabbit.rmi.remote.RemoteRow;
  */
 public class ClientRowIterator extends ClientIterator implements RowIterator {
 
+    /** Current session. */
+    private Session session;
+
     /**
      * Creates a ClientRowIterator instance.
      *
      * @param iterator      remote iterator
      * @param factory       local adapter factory
      */
-    public ClientRowIterator(
+    public ClientRowIterator(Session session,
             RemoteIterator iterator, LocalAdapterFactory factory) {
         super(iterator, factory);
+        this.session = session;
     }
 
     /**
@@ -47,7 +52,7 @@ public class ClientRowIterator extends ClientIterator implements RowIterator {
      * @see ClientIterator#getObject(Object)
      */
     protected Object getObject(Object remote) {
-        return getFactory().getRow((RemoteRow) remote);
+        return getFactory().getRow(session, (RemoteRow) remote);
     }
 
     /**
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/security/ClientPrivilege.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/security/ClientPrivilege.java
index 21b5fe2..b8a6450 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/security/ClientPrivilege.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/client/security/ClientPrivilege.java
@@ -108,8 +108,4 @@ public class ClientPrivilege extends ClientObject implements Privilege {
     public int hashCode() {
         return getName().hashCode();
     }
-
-    RemotePrivilege getRemotePrivilege() {
-        return rp;
-    }
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/observation/ClientEventPoll.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/observation/ClientEventPoll.java
index 9f1a390..d11daa5 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/observation/ClientEventPoll.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/observation/ClientEventPoll.java
@@ -22,11 +22,12 @@ import java.util.Map;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
-import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.observation.Event;
 import javax.jcr.observation.EventIterator;
 import javax.jcr.observation.EventListener;
 
+import org.apache.jackrabbit.rmi.client.RemoteRepositoryException;
+import org.apache.jackrabbit.rmi.client.RemoteRuntimeException;
 import org.apache.jackrabbit.rmi.iterator.ArrayEventIterator;
 import org.apache.jackrabbit.rmi.remote.RemoteEventCollection;
 import org.apache.jackrabbit.rmi.remote.RemoteObservationManager;
@@ -261,15 +262,9 @@ public class ClientEventPoll extends Thread {
      */
     private static class JCREvent implements Event {
 
-        /** The event type */
-        private final int type;
-
-        /** The path of the repository item underlying the event */
-        private final String path;
-
-        /** The user id of the session originating the event */
-        private final String userID;
-
+    	/** The adapted remote event. */
+    	private final RemoteEventCollection.RemoteEvent remote;
+    	
         /**
          * Creates an instance of this class from the contents of the given
          * <code>remoteEvent</code>.
@@ -280,40 +275,70 @@ public class ClientEventPoll extends Thread {
          * @throws RemoteException if an RMI error occurrs.
          */
         private JCREvent(RemoteEventCollection.RemoteEvent remoteEvent) throws RemoteException {
-            type = remoteEvent.getType();
-            path = remoteEvent.getPath();
-            userID = remoteEvent.getUserID();
+            remote = remoteEvent;
         }
 
         /** {@inheritDoc} */
         public int getType() {
-            return type;
+            try {
+                return remote.getType();
+            } catch (RemoteException ex) {
+                throw new RemoteRuntimeException(ex);
+            }
         }
 
         /** {@inheritDoc} */
-        public String getPath() {
-            return path;
+        public String getPath() throws RepositoryException {
+            try {
+                return remote.getPath();
+            } catch (RemoteException ex) {
+                throw new RemoteRepositoryException(ex);
+            }
         }
 
         /** {@inheritDoc} */
         public String getUserID() {
-            return userID;
+            try {
+                return remote.getUserID();
+            } catch (RemoteException ex) {
+                throw new RemoteRuntimeException(ex);
+            }
         }
 
+        /** {@inheritDoc} */
         public long getDate() throws RepositoryException {
-            throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+            try {
+                return remote.getDate();
+            } catch (RemoteException ex) {
+                throw new RemoteRepositoryException(ex);
+            }
         }
 
+        /** {@inheritDoc} */
         public String getIdentifier() throws RepositoryException {
-            throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+            try {
+                return remote.getIdentifier();
+            } catch (RemoteException ex) {
+                throw new RemoteRepositoryException(ex);
+            }
         }
 
+        /** {@inheritDoc} */
         public Map getInfo() throws RepositoryException {
-            throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+            try {
+                return remote.getInfo();
+            } catch (RemoteException ex) {
+                throw new RemoteRepositoryException(ex);
+            }
         }
 
+        /** {@inheritDoc} */
         public String getUserData() throws RepositoryException {
-            throw new UnsupportedRepositoryOperationException("TODO: JCRRMI-26");
+            try {
+                return remote.getUserData();
+            } catch (RemoteException ex) {
+                throw new RemoteRepositoryException(ex);
+            }
         }
     }
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteEventCollection.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteEventCollection.java
index e356563..5a90dfe 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteEventCollection.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteEventCollection.java
@@ -18,6 +18,9 @@ package org.apache.jackrabbit.rmi.remote;
 
 import java.rmi.Remote;
 import java.rmi.RemoteException;
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
 
 /**
  * The <code>RemoteEventCollection</code> class serves as a container for
@@ -52,28 +55,75 @@ public interface RemoteEventCollection extends Remote {
      */
     public static interface RemoteEvent extends Remote {
         /**
-         * Returns the event type.
+         * Remote version of the
+         * {@link javax.jcr.Event#getType() Event.getType()} method.
          *
-         * @return event type
+         * @return the type of this event.
          * @throws RemoteException on RMI errors
          */
         int getType() throws RemoteException;
 
         /**
-         * Returns the absolute path of the underlying item.
+         * Remote version of the
+         * {@link javax.jcr.Event#getPath() Event.getPath()} method.
          *
-         * @return item path
+         * @return the absolute path associated with this event or
+         *         <code>null</code>.
+         * @throws RepositoryException on repository errors
          * @throws RemoteException on RMI errors
          */
-        String getPath() throws RemoteException;
+        String getPath() throws RepositoryException, RemoteException;
 
         /**
-         * Returns the userID of the session causing this event.
+         * Remote version of the
+         * {@link javax.jcr.Event#getUserID() Event.getUserID()} method.
          *
-         * @return user identifier
+         * @return the user ID.
          * @throws RemoteException on RMI errors
          */
         String getUserID() throws RemoteException;
+        
+        /**
+         * Remote version of the
+         * {@link javax.jcr.Event#getIdentifier() Event.getIdentifier()} method.
+         *
+         * @return the identifier associated with this event or <code>null</code>.
+         * @throws RepositoryException on repository errors
+         * @throws RemoteException on RMI errors
+         */
+        String getIdentifier() throws RepositoryException, RemoteException;
+        
+        /**
+         * Remote version of the
+         * {@link javax.jcr.Event#getInfo() Event.getInfo()} method.
+         *
+         * @return A <code>Map</code> containing parameter information for instances
+     *         of a <code>NODE_MOVED</code> event.
+         * @throws RepositoryException on repository errors
+         * @throws RemoteException on RMI errors
+         */
+        Map getInfo() throws RepositoryException, RemoteException;
+        
+        /**
+         * Remote version of the
+         * {@link javax.jcr.Event#getUserData() Event.getUserData()} method.
+         *
+         * @return The user data string.
+         * @throws RepositoryException on repository errors
+         * @throws RemoteException on RMI errors
+         */
+        String getUserData() throws RepositoryException, RemoteException;
+        
+        /**
+         * Remote version of the
+         * {@link javax.jcr.Event#getDate() Event.getDate()} method.
+         *
+         * @return the date when the change was persisted that caused this event.
+         * @throws RepositoryException on repository errors
+         * @throws RemoteException on RMI errors
+         */
+        long getDate() throws RepositoryException, RemoteException;
+        
     }
 
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteLock.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteLock.java
index b2e8340..014c69c 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteLock.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteLock.java
@@ -108,5 +108,24 @@ public interface RemoteLock extends Remote {
      */
     boolean isSessionScoped() throws RemoteException;
 
+    /**
+     * Remote version of the
+     * {@link javax.jcr.lock.Lock#getSecondsRemaining()} () Lock.getSecondsRemaining()} method.
+     *
+     * @return the number of seconds remaining until this lock times out.
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+	long getSecondsRemaining() throws RepositoryException, RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.lock.Lock#isLockOwningSession()} () Lock.isLockOwningSession()} method.
+     *
+     * @return a <code>boolean</code>.
+     * @throws RemoteException on RMI errors
+     */
+	boolean isLockOwningSession() throws RemoteException;
+
 
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteNode.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteNode.java
index 86531f0..e95a047 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteNode.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteNode.java
@@ -652,4 +652,92 @@ public interface RemoteNode extends RemoteItem {
      */
     RemoteLock getLock() throws RepositoryException, RemoteException;
 
+    /**
+     * Remote version of the
+     * {@link javax.jcr.Node#getSharedSet() Node.getSharedSet()} method.
+     *
+     * @return a <code>NodeIterator</code>.
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+	RemoteIterator getSharedSet() throws RepositoryException, RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.Node#followLifecycleTransition(String) Node.followLifecycleTransition(String)}
+     * method.
+     *
+     * @param transition a state transition
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+	void followLifecycleTransition(String transition) throws RepositoryException, RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.Node#getAllowedLifecycleTransistions() Node.getAllowedLifecycleTransistions()}
+     * method.
+     *
+     * @return a <code>String</code> array.
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+	String[] getAllowedLifecycleTransistions() throws RepositoryException, RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.Node#getWeakReferences() Node.getWeakReferences()}
+     * method.
+     *
+     * @return A <code>PropertyIterator</code>.
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+	RemoteIterator getWeakReferences() throws RepositoryException, RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.Node#getWeakReferences(String) Node.getWeakReferences(String)}
+     * method.
+     *
+     * @param name name of referring <code>WEAKREFERENCE</code> properties to be
+     *             returned; if <code>null</code> then all referring
+     *             <code>WEAKREFERENCE</code>s are returned.
+     * @return A <code>PropertyIterator</code>.
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+	RemoteIterator getWeakReferences(String name) throws RepositoryException, RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.Node#removeShare() Node.removeShare()}
+     * method.
+     *
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+	void removeShare() throws RepositoryException, RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.Node#removeSharedSet() Node.removeSharedSet()}
+     * method.
+     *
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+	void removeSharedSet() throws RepositoryException, RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.Node#setPrimaryType(String) Node.setPrimaryType(String)}
+     * method.
+     *
+     * @param nodeTypeName the node type name
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+	void setPrimaryType(String nodeTypeName) throws RepositoryException, RemoteException;
+
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteNodeDefinition.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteNodeDefinition.java
index 7d69600..a4c14c2 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteNodeDefinition.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteNodeDefinition.java
@@ -71,4 +71,24 @@ public interface RemoteNodeDefinition extends RemoteItemDefinition {
      */
     boolean allowsSameNameSiblings() throws RemoteException;
 
+    /**
+     * Remote version of the
+     * {@link javax.jcr.nodetype.NodeDefinition#getDefaultPrimaryTypeName() NodeDef.getDefaultPrimaryTypeName()}
+     * method.
+     *
+     * @return a String
+     * @throws RemoteException on RMI errors
+     */
+	String getDefaultPrimaryTypeName() throws RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.nodetype.NodeDefinition#getRequiredPrimaryTypeNames() NodeDef.getRequiredPrimaryTypeNames()}
+     * method.
+     *
+     * @return a String array
+     * @throws RemoteException on RMI errors
+     */
+	String[] getRequiredPrimaryTypeNames() throws RemoteException;
+
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteNodeType.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteNodeType.java
index 16bc8cc..152011c 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteNodeType.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteNodeType.java
@@ -227,4 +227,64 @@ public interface RemoteNodeType extends Remote {
      */
     String getPrimaryItemName() throws RemoteException;
 
+    /**
+     * Remote version of the
+     * {@link javax.jcr.nodetype.NodeType#canRemoveNode() NodeType.canRemoveNode()}
+     * method.
+     *
+     * @return boolean
+     * @throws RemoteException on RMI errors
+     */
+	boolean canRemoveNode(String nodeName) throws RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.nodetype.NodeType#canRemoveProperty() NodeType.canRemoveProperty()}
+     * method.
+     *
+     * @return boolean
+     * @throws RemoteException on RMI errors
+     */
+	boolean canRemoveProperty(String propertyName) throws RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.nodetype.NodeType#getDeclaredSupertypeNames() NodeType.getDeclaredSupertypeNames()}
+     * method.
+     *
+     * @return a String[]
+     * @throws RemoteException on RMI errors
+     */
+	String[] getDeclaredSupertypeNames() throws RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.nodetype.NodeType#isQueryable() NodeType.isQueryable()}
+     * method.
+     *
+     * @return boolean
+     * @throws RemoteException on RMI errors
+     */
+	boolean isQueryable() throws RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.nodetype.NodeType#getDeclaredSubtypes() NodeType.getDeclaredSubtypes()}
+     * method.
+     *
+     * @return RemoteIterator
+     * @throws RemoteException on RMI errors
+     */
+	RemoteIterator getDeclaredSubtypes() throws RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.nodetype.NodeType#getSubtypes() NodeType.getSubtypes()}
+     * method.
+     *
+     * @return RemoteIterator
+     * @throws RemoteException on RMI errors
+     */
+	RemoteIterator getSubtypes() throws RemoteException;
+
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemotePropertyDefinition.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemotePropertyDefinition.java
index 75c07bd..3219b15 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemotePropertyDefinition.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemotePropertyDefinition.java
@@ -86,4 +86,34 @@ public interface RemotePropertyDefinition extends RemoteItemDefinition {
      */
     boolean isMultiple() throws RemoteException;
 
+    /**
+     * Remote version of the
+     * {@link javax.jcr.nodetype.PropertyDefinition#getAvailableQueryOperators() PropertyDefinition.getAvailableQueryOperators()}
+     * method.
+     *
+     * @return a String[]
+     * @throws RemoteException on RMI errors
+     */
+	String[] getAvailableQueryOperators() throws RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.nodetype.PropertyDefinition#isFullTextSearchable() PropertyDefinition.isFullTextSearchable()}
+     * method.
+     *
+     * @return a boolean
+     * @throws RemoteException on RMI errors
+     */
+	boolean isFullTextSearchable() throws RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.nodetype.PropertyDefinition#isQueryOrderable() PropertyDefinition.isQueryOrderable()}
+     * method.
+     *
+     * @return a boolean
+     * @throws RemoteException on RMI errors
+     */
+	boolean isQueryOrderable() throws RemoteException;
+
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteQuery.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteQuery.java
index ab995cc..7a4e9eb 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteQuery.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteQuery.java
@@ -20,6 +20,7 @@ import java.rmi.Remote;
 import java.rmi.RemoteException;
 
 import javax.jcr.RepositoryException;
+import javax.jcr.Value;
 
 /**
  * Remote version of the JCR {@link javax.jcr.query.Query Query} interface.
@@ -45,6 +46,22 @@ public interface RemoteQuery extends Remote {
     RemoteQueryResult execute() throws RepositoryException, RemoteException;
 
     /**
+     * @see javax.jcr.query.Query#setLimit()
+     *
+     * @param limit a <code>long</code>
+     * @throws RemoteException on RMI errors
+     */
+    void setLimit(long limit) throws RemoteException;
+    
+    /**
+     * @see javax.jcr.query.Query#setOffset()
+     *
+     * @param offset a <code>long</code>
+     * @throws RemoteException on RMI errors
+     */
+    void setOffset(long offset) throws RemoteException;
+    
+    /**
      * @see javax.jcr.query.Query#getStatement()
      *
      * @return the query statement.
@@ -79,4 +96,22 @@ public interface RemoteQuery extends Remote {
      */
     RemoteNode storeAsNode(String absPath) throws RepositoryException, RemoteException;
 
+    /**
+     * @see javax.jcr.query.Query#bindValue(String, Value)
+     *
+     * @param varName name of variable in query
+     * @param value   value to bind
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+    void bindValue(String varName, Value value) throws RepositoryException, RemoteException;
+    
+    /**
+     * @see javax.jcr.query.Query#getBindVariableNames()
+     *
+     * @return the names of the bind variables in this query.
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+    public String[] getBindVariableNames() throws RepositoryException, RemoteException;
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteQueryResult.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteQueryResult.java
index 8926c38..5fe8691 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteQueryResult.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteQueryResult.java
@@ -61,4 +61,12 @@ public interface RemoteQueryResult extends Remote {
      */
     RemoteIterator getNodes() throws RepositoryException, RemoteException;
 
+    /**
+     * @see javax.jcr.query.QueryResult#getSelectorNames()
+     *
+     * @return a <code>String</code> array holding the selector names.
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+    public String[] getSelectorNames() throws RepositoryException, RemoteException;
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteRepository.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteRepository.java
index 732d0be..46baccd 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteRepository.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteRepository.java
@@ -21,6 +21,7 @@ import java.rmi.RemoteException;
 
 import javax.jcr.Credentials;
 import javax.jcr.RepositoryException;
+import javax.jcr.Value;
 
 /**
  * Remote version of the JCR {@link javax.jcr.Repository Repository} interface.
@@ -115,4 +116,45 @@ public interface RemoteRepository extends Remote {
     RemoteSession login(Credentials credentials, String workspace)
             throws RepositoryException, RemoteException;
 
+    /**
+     * Remote version of the
+     * {@link javax.jcr.Repository#getDescriptorValue(String) Repository.getDescriptorValue(String)}
+     * method.
+     *
+     * @return descriptor value
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+	Value getDescriptorValue(String key) throws RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.Repository#getDescriptorValues(String) Repository.getDescriptorValues(String)}
+     * method.
+     *
+     * @return descriptor value array
+     * @throws RemoteException on RMI errors
+     */
+	Value[] getDescriptorValues(String key) throws RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.Repository#isSingleValueDescriptor(String) Repository.isSingleValueDescriptor(String)}
+     * method.
+     *
+     * @return boolean
+     * @throws RemoteException on RMI errors
+     */
+	boolean isSingleValueDescriptor(String key) throws RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.Repository#isStandardDescriptor(String) Repository.isStandardDescriptor(String)}
+     * method.
+     *
+     * @return boolean
+     * @throws RemoteException on RMI errors
+     */
+	boolean isStandardDescriptor(String key) throws RemoteException;
+
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteRow.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteRow.java
index 11ad567..0aa8365 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteRow.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteRow.java
@@ -55,4 +55,61 @@ public interface RemoteRow extends Remote {
      */
     Value getValue(String propertyName)
             throws RepositoryException, RemoteException;
+
+    /**
+     * @see javax.jcr.query.Row#getNode()
+     *
+     * @return a node
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+	RemoteNode getNode() throws RepositoryException, RemoteException;
+
+    /**
+     * @see javax.jcr.query.Row#getNode(String)
+     *
+     * @param selectorName
+     * @return a node
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+	RemoteNode getNode(String selectorName) throws RepositoryException, RemoteException;
+
+    /**
+     * @see javax.jcr.query.Row#getPath()
+     *
+     * @return the path
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+	String getPath() throws RepositoryException, RemoteException;
+
+    /**
+     * @see javax.jcr.query.Row#getPath(String)
+     *
+     * @param selectorName
+     * @return the path
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+	String getPath(String selectorName) throws RepositoryException, RemoteException;
+
+    /**
+     * @see javax.jcr.query.Row#getScore()
+     *
+     * @return the score
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+	double getScore() throws RepositoryException, RemoteException;
+
+    /**
+     * @see javax.jcr.query.Row#getScore(String)
+     *
+     * @param selectorName
+     * @return the score
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+	double getScore(String selectorName) throws RepositoryException, RemoteException;
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteVersion.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteVersion.java
index b707afa..5598fa2 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteVersion.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteVersion.java
@@ -21,7 +21,6 @@ import java.util.Calendar;
 
 import javax.jcr.RepositoryException;
 
-
 /**
  * Remote version of the JCR {@link javax.jcr.version.Version Version} interface.
  * Used by the {@link org.apache.jackrabbit.rmi.server.ServerVersion ServerVersion}
@@ -52,7 +51,7 @@ public interface RemoteVersion extends RemoteNode {
      * @throws RepositoryException on repository errors
      * @throws RemoteException on RMI errors
      */
-//     RemoteVersionHistory getContainingHistory() throws RepositoryException;
+     RemoteVersionHistory getContainingHistory() throws RepositoryException, RemoteException;
 
     /**
      * Remote version of the
@@ -66,6 +65,18 @@ public interface RemoteVersion extends RemoteNode {
 
     /**
      * Remote version of the
+     * {@link javax.jcr.version.Version#getLinearSuccessor() Version.getLinearSuccessor()} method.
+     *
+     * @return a <code>RemoteVersion</code> or <code>null</code> if no linear
+     *         successor exists.
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     * @see RemoteVersionHistory#getAllLinearVersions
+     */
+    RemoteVersion getLinearSuccessor() throws RepositoryException, RemoteException;
+
+    /**
+     * Remote version of the
      * {@link javax.jcr.version.Version#getSuccessors() Version.getSuccessors()} method.
      *
      * @return a <code>RemoteVersion</code> array.
@@ -76,6 +87,18 @@ public interface RemoteVersion extends RemoteNode {
 
     /**
      * Remote version of the
+     * {@link javax.jcr.version.Version#getLinearPredecessor() Version.getLinearPredecessor()} method.
+     *
+     * @return a <code>RemoteVersion</code> or <code>null</code> if no linear
+     *         predecessor exists.
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     * @see RemoteVersionHistory#getAllLinearVersions
+     */
+    RemoteVersion getLinearPredecessor() throws RepositoryException, RemoteException;
+
+    /**
+     * Remote version of the
      * {@link javax.jcr.version.Version#getPredecessors() Version.getPredecessors()} method.
      *
      * @return a <code>RemoteVersion</code> array.
@@ -86,14 +109,11 @@ public interface RemoteVersion extends RemoteNode {
 
     /**
      * Remote version of the
-     * {@link javax.jcr.version.Version#getContainingHistory()} Version.getContainingHistory()} method.
+     * {@link javax.jcr.version.Version#getFrozenNode() Version.getFrozenNode()} method.
      *
-     * @return a <code>RemoteVersionHistory</code>.
+     * @return a <code>RemoteNode</code> object.
      * @throws RepositoryException on repository errors
      * @throws RemoteException on RMI errors
      */
-    RemoteVersionHistory getContainingHistory() throws RepositoryException, RemoteException;
-
-
-
+    RemoteNode getFrozenNode() throws RepositoryException, RemoteException;
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteVersionHistory.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteVersionHistory.java
index 384d44a..6bcc0ef 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteVersionHistory.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteVersionHistory.java
@@ -46,17 +46,30 @@ public interface RemoteVersionHistory extends RemoteNode {
 
     /**
      * Remote version of the
-     * {@link javax.jcr.version.VersionHistory#getVersionableUUID() VersionHistory.getVersionableUUID()}
+     * {@link javax.jcr.version.VersionHistory#getVersionableUUID()}  VersionHistory.getVersionableUUID()}
      * method.
      *
-     * @return the UUID of the versionable node for which this is the version history.
+     * @return the uuid of the versionable node
      * @throws RepositoryException if an error occurs.
      * @throws RemoteException on RMI errors
+     * @deprecated As of JCR 2.0, {@link #getVersionableIdentifier} should be
+     *             used instead.
      */
-//    String getVersionableUUID() throws RepositoryException, RemoteException;
+    String getVersionableUUID() throws RepositoryException, RemoteException;
 
     /**
      * Remote version of the
+     * {@link javax.jcr.version.VersionHistory#getVersionableIdentifier()}  VersionHistory.getVersionableIdentifier()}
+     * method.
+     *
+     * @return the identifier of the versionable node
+     * @throws RepositoryException if an error occurs.
+     * @throws RemoteException on RMI errors
+     */
+    String getVersionableIdentifier() throws RepositoryException, RemoteException;
+	
+    /**
+     * Remote version of the
      * {@link javax.jcr.version.VersionHistory#getRootVersion() VersionHistory.getRootVersion()}
      * method.
      *
@@ -68,6 +81,17 @@ public interface RemoteVersionHistory extends RemoteNode {
 
     /**
      * Remote version of the
+     * {@link javax.jcr.version.VersionHistory#getAllLinearVersions() VersionHistory.getAllLinearVersions()}
+     * method.
+     *
+     * @return linear remote versions
+     * @throws RepositoryException if an error occurs.
+     * @throws RemoteException on RMI errors
+     */
+    RemoteIterator getAllLinearVersions() throws RepositoryException, RemoteException;
+
+    /**
+     * Remote version of the
      * {@link javax.jcr.version.VersionHistory#getAllVersions() VersionHistory.getAllVersions()}
      * method.
      *
@@ -79,6 +103,28 @@ public interface RemoteVersionHistory extends RemoteNode {
 
     /**
      * Remote version of the
+     * {@link javax.jcr.version.VersionHistory#getAllLinearFrozenNodes() VersionHistory.getAllLinearFrozenNodes()}
+     * method.
+     *
+     * @return linear remote frozen nodes
+     * @throws RepositoryException if an error occurs.
+     * @throws RemoteException on RMI errors
+     */
+    RemoteIterator getAllLinearFrozenNodes() throws RepositoryException, RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.version.VersionHistory#getAllFrozenNodes() VersionHistory.getAllFrozenNodes()}
+     * method.
+     *
+     * @return remote frozen nodes
+     * @throws RepositoryException if an error occurs.
+     * @throws RemoteException on RMI errors
+     */
+    RemoteIterator getAllFrozenNodes() throws RepositoryException, RemoteException;
+
+    /**
+     * Remote version of the
      * {@link javax.jcr.version.VersionHistory#getVersion(String) VersionHistory.getVersion(String)}
      * method.
      *
@@ -195,16 +241,4 @@ public interface RemoteVersionHistory extends RemoteNode {
     void removeVersion(String versionName)
             throws RepositoryException, RemoteException;
 
-
-    /**
-     * Remote version of the
-     * {@link javax.jcr.version.VersionHistory#getVersionableUUID()}  VersionHistory.getVersionableUUID()}
-     * method.
-     *
-     * @return the uuid of the versionable node
-     * @throws RepositoryException if another error occurs.
-     * @throws RemoteException on RMI errors
-     */
-    String getVersionableUUID() throws RepositoryException, RemoteException;
-
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteVersionManager.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteVersionManager.java
index c7fd90d..626be07 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteVersionManager.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/RemoteVersionManager.java
@@ -23,69 +23,87 @@ import javax.jcr.RepositoryException;
 
 public interface RemoteVersionManager extends Remote {
 
-//    void cancelMerge(String absPath, Version version)
-//        throws RepositoryException, RemoteException;
-
-    RemoteVersion checkin(String absPath)
-        throws RepositoryException, RemoteException;
-
+    /**
+     * Remote version of the
+     * {@link javax.jcr.version.VersionManager#checkin(String) VersionManager.checkin(String)}
+     * method.
+     *
+     * @param absPath an absolute path.
+     * @return the created version.
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+    RemoteVersion checkin(String absPath) throws RepositoryException, RemoteException;
+
+    /**
+     * Remote version of the
+     * {@link javax.jcr.version.VersionManager#checkout(String) VersionManager.checkout(String)}
+     * method.
+     *
+     * @param absPath an absolute path.
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
     void checkout(String absPath) throws RepositoryException, RemoteException;
 
-    RemoteVersion checkpoint(String absPath)
-        throws RepositoryException, RemoteException;
+    /**
+     * Remote version of the
+     * {@link javax.jcr.version.VersionManager#checkpoint(String) VersionManager.checkpoint(String)}
+     * method.
+     *
+     * @param absPath an absolute path.
+     * @return the created version.
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+    RemoteVersion checkpoint(String absPath) throws RepositoryException, RemoteException;
 
-    RemoteNode createActivity(String title)
-        throws RepositoryException, RemoteException;
+    /**
+     * Remote version of the
+     * {@link javax.jcr.version.VersionManager#isCheckedOut(String) VersionManager.isCheckedOut(String)}
+     * method.
+     *
+     * @param absPath an absolute path.
+     * @return a boolean
+     * @throws RepositoryException on repository errors
+     * @throws RemoteException on RMI errors
+     */
+    boolean isCheckedOut(String absPath) throws RepositoryException, RemoteException;
 
-    RemoteNode createConfiguration(String absPath)
-        throws RepositoryException, RemoteException;
+    RemoteVersionHistory getVersionHistory(String absPath) throws RepositoryException, RemoteException;
 
-//    void doneMerge(String absPath, Version version)
-//        throws RepositoryException, RemoteException;
+    RemoteVersion getBaseVersion(String absPath) throws RepositoryException, RemoteException;
 
-    RemoteNode getActivity() throws RepositoryException, RemoteException;
+    void restore(String[] versionIdentifiers, boolean removeExisting) throws RepositoryException, RemoteException;
 
-    RemoteVersion getBaseVersion(String absPath)
-        throws RepositoryException, RemoteException;
+    void restore(String absPath, String versionName, boolean removeExisting) throws RepositoryException, RemoteException;
 
-    RemoteVersionHistory getVersionHistory(String absPath)
-        throws RepositoryException, RemoteException;
+    void restore(String versionIdentifier, boolean removeExisting) throws RepositoryException, RemoteException;
 
-    boolean isCheckedOut(String absPath)
-        throws RepositoryException, RemoteException;
+    void restoreVI(String absPath, String versionIdentifier, boolean removeExisting) throws RepositoryException, RemoteException;
 
-//    NodeIterator merge(Node activityNode)
-//        throws RepositoryException, RemoteException;
+    void restoreByLabel(String absPath, String versionLabel, boolean removeExisting) throws RepositoryException, RemoteException;
 
-    RemoteIterator merge(
-        String absPath, String srcWorkspace, boolean bestEffort)
-        throws RepositoryException, RemoteException;
+    RemoteIterator merge(String absPath, String srcWorkspace, boolean bestEffort)
+            throws RepositoryException, RemoteException;
 
-    RemoteIterator merge(
-        String absPath, String srcWorkspace, boolean bestEffort,
-        boolean isShallow)
-        throws RepositoryException, RemoteException;
+    RemoteIterator merge(String absPath, String srcWorkspace, boolean bestEffort, boolean isShallow)
+            throws RepositoryException, RemoteException;
 
-//    void removeActivity(Node activityNode)
-//        throws RepositoryException, RemoteException;
+    void doneMerge(String absPath, String versionIdentifier) throws RepositoryException, RemoteException;
+    
+    void cancelMerge(String absPath, String versionIdentifier) throws RepositoryException, RemoteException;
 
-//    void restore(Version[] versions, boolean removeExisting)
-//        throws RepositoryException, RemoteException;
+    RemoteNode createConfiguration(String absPath) throws RepositoryException, RemoteException;
 
-//    void restore(Version version, boolean removeExisting)
-//        throws RepositoryException, RemoteException;
+    RemoteNode setActivity(String activityNodeIdentifier) throws RepositoryException, RemoteException;
 
-    void restore(
-        String absPath, String versionName, boolean removeExisting)
-        throws RepositoryException, RemoteException;
+    RemoteNode getActivity() throws RepositoryException, RemoteException;
 
-//    void restore(String absPath, Version version, boolean removeExisting)
-//        throws RepositoryException, RemoteException;
+    RemoteNode createActivity(String title) throws RepositoryException, RemoteException;
 
-    void restoreByLabel(
-        String absPath, String versionLabel, boolean removeExisting)
-        throws RepositoryException, RemoteException;
+    void removeActivity(String activityNodeIdentifier) throws RepositoryException, RemoteException;
 
-//    Node setActivity(Node activity) throws RepositoryException, RemoteException;
+    RemoteIterator merge(String activityNodeIdentifier) throws RepositoryException, RemoteException;
 
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/SerializableXid.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/SerializableXid.java
index 2a4021b..83bd352 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/SerializableXid.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/remote/SerializableXid.java
@@ -1,71 +1,71 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.rmi.remote;
-
-import java.io.Serializable;
-import java.util.Arrays;
-
-import javax.transaction.xa.Xid;
-
-/**
- * Serializable {@link Xid}.
- *
- * @since Jackrabbit JCR-RMI 1.5
- */
-public class SerializableXid implements Serializable, Xid {
-
-    private final int formatId;
-
-    private final byte[] globalTransactionId;
-
-    private final byte[] branchQualifier;
-
-    private final int hashCode;
-
-    public SerializableXid(Xid xid) {
-        formatId = xid.getFormatId();
-        globalTransactionId = xid.getGlobalTransactionId();
-        branchQualifier = xid.getBranchQualifier();
-        hashCode = xid.hashCode();
-    }
-
-    public int getFormatId() {
-        return formatId;
-    }
-
-    public byte[] getGlobalTransactionId() {
-        return globalTransactionId;
-    }
-
-    public byte[] getBranchQualifier() {
-        return branchQualifier;
-    }
-
-    public int hashCode() {
-        return hashCode;
-    }
-
-    public boolean equals(Object xid) {
-        return (xid instanceof Xid)
-            && formatId == ((Xid) xid).getFormatId()
-            && Arrays.equals(
-                    globalTransactionId, ((Xid) xid).getGlobalTransactionId())
-            && Arrays.equals(
-                    branchQualifier, ((Xid) xid).getBranchQualifier());
-    }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.rmi.remote;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import javax.transaction.xa.Xid;
+
+/**
+ * Serializable {@link Xid}.
+ *
+ * @since Jackrabbit JCR-RMI 1.5
+ */
+public class SerializableXid implements Serializable, Xid {
+
+    private final int formatId;
+
+    private final byte[] globalTransactionId;
+
+    private final byte[] branchQualifier;
+
+    private final int hashCode;
+
+    public SerializableXid(Xid xid) {
+        formatId = xid.getFormatId();
+        globalTransactionId = xid.getGlobalTransactionId();
+        branchQualifier = xid.getBranchQualifier();
+        hashCode = xid.hashCode();
+    }
+
+    public int getFormatId() {
+        return formatId;
+    }
+
+    public byte[] getGlobalTransactionId() {
+        return globalTransactionId;
+    }
+
+    public byte[] getBranchQualifier() {
+        return branchQualifier;
+    }
+
+    public int hashCode() {
+        return hashCode;
+    }
+
+    public boolean equals(Object xid) {
+        return (xid instanceof Xid)
+            && formatId == ((Xid) xid).getFormatId()
+            && Arrays.equals(
+                    globalTransactionId, ((Xid) xid).getGlobalTransactionId())
+            && Arrays.equals(
+                    branchQualifier, ((Xid) xid).getBranchQualifier());
+    }
+
+}
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/repository/AbstractRemoteRepositoryFactory.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/repository/AbstractRemoteRepositoryFactory.java
index a2d929e..1a8be8d 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/repository/AbstractRemoteRepositoryFactory.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/repository/AbstractRemoteRepositoryFactory.java
@@ -42,7 +42,6 @@ public abstract class AbstractRemoteRepositoryFactory
      * Creates a factory for looking up a repository from the given RMI URL.
      *
      * @param factory local adapter factory
-     * @param url RMI URL of the repository
      */
     protected AbstractRemoteRepositoryFactory(LocalAdapterFactory factory) {
         this.factory = factory;
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/repository/RmiRepositoryFactory.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/repository/RmiRepositoryFactory.java
index c34a6c6..a4c1c85 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/repository/RmiRepositoryFactory.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/repository/RmiRepositoryFactory.java
@@ -36,6 +36,8 @@ import javax.naming.InitialContext;
 import javax.naming.NamingException;
 
 import org.apache.jackrabbit.rmi.client.ClientAdapterFactory;
+import org.apache.jackrabbit.rmi.client.LocalAdapterFactory;
+import org.apache.jackrabbit.rmi.client.SafeClientRepository;
 import org.apache.jackrabbit.rmi.remote.RemoteRepository;
 
 public class RmiRepositoryFactory implements RepositoryFactory {
@@ -73,75 +75,101 @@ public class RmiRepositoryFactory implements RepositoryFactory {
         }
     }
 
-    private Repository getUrlRepository(URL url) throws RepositoryException {
-        try {
-            InputStream stream = url.openStream();
-            try {
-                Object remote = new ObjectInputStream(stream).readObject();
-                if (remote instanceof RemoteRepository) {
-                    return getRepository((RemoteRepository) remote);
-                } else {
-                    throw new RepositoryException(
-                            "The resource at URL " + url
-                            + " is not a remote repository stub: "
-                            + remote);
+    private Repository getUrlRepository(final URL url)
+            throws RepositoryException {
+        return new RmiSafeClientRepository(new ClientAdapterFactory()) {
+
+            @Override
+            protected RemoteRepository getRemoteRepository() throws RemoteException {
+                try {
+                    InputStream stream = url.openStream();
+                    try {
+                        Object remote = new ObjectInputStream(stream).readObject();
+                        if (remote instanceof RemoteRepository) {
+                            return (RemoteRepository) remote;
+                        } else {
+                            throw new RemoteException("The resource at URL " + url
+                                    + " is not a remote repository stub: " + remote);
+                        }
+                    } finally {
+                        if (stream != null) {
+                            stream.close();
+                        }
+                    }
+                } catch (ClassNotFoundException e) {
+                    throw new RemoteException("The resource at URL " + url
+                            + " requires a class that is not available", e);
+                } catch (IOException e) {
+                    throw new RemoteException("Failed to read the resource at URL "
+                            + url, e);
                 }
-            } finally {
-                stream.close();
             }
-        } catch (ClassNotFoundException e) {
-            throw new RepositoryException(
-                    "The resource at URL " + url
-                    + " requires a class that is not available", e);
-        } catch (IOException e) {
-            throw new RepositoryException(
-                    "Failed to read the resource at URL " + url, e);
-        }
+        };
     }
 
     @SuppressWarnings("rawtypes")
-    private Repository getJndiRepository(String name, Hashtable environment)
-            throws RepositoryException {
-        try {
-            Object value = new InitialContext(environment).lookup(name);
-            if (value instanceof RemoteRepository) {
-                return getRepository((RemoteRepository) value);
-            } else {
-                throw new RepositoryException(
-                        "The JNDI resource " + name
-                        + " is not a remote repository stub: " + value);
+    private Repository getJndiRepository(final String name,
+            final Hashtable environment) throws RepositoryException {
+        return new RmiSafeClientRepository(new ClientAdapterFactory()) {
+
+            @Override
+            protected RemoteRepository getRemoteRepository() throws RemoteException {
+                try {
+                    Object value = new InitialContext(environment).lookup(name);
+                    if (value instanceof RemoteRepository) {
+                        return (RemoteRepository) value;
+                    } else {
+                        throw new RemoteException("The JNDI resource " + name
+                                + " is not a remote repository stub: " + value);
+                    }
+                } catch (NamingException e) {
+                    throw new RemoteException(
+                            "Failed to look up the JNDI resource " + name, e);
+                }
             }
-        } catch (NamingException e) {
-            throw new RepositoryException(
-                    "Failed to look up the JNDI resource " + name, e);
-        }
+        };
     }
 
-    private Repository getRmiRepository(String name)
+    private Repository getRmiRepository(final String name)
             throws RepositoryException {
-        try {
-            Object value = Naming.lookup(name);
-            if (value instanceof RemoteRepository) {
-                return getRepository((RemoteRepository) value);
-            } else {
-                throw new RepositoryException(
-                        "The RMI resource " + name
-                        + " is not a remote repository stub: " + value);
+        return new RmiSafeClientRepository(new ClientAdapterFactory()) {
+
+            @Override
+            protected RemoteRepository getRemoteRepository() throws RemoteException {
+                try {
+                    Object value = Naming.lookup(name);
+                    if (value instanceof RemoteRepository) {
+                        return (RemoteRepository) value;
+                    } else {
+                        throw new RemoteException("The RMI resource " + name
+                                + " is not a remote repository stub: " + value);
+                    }
+                } catch (NotBoundException e) {
+                    throw new RemoteException(
+                            "RMI resource " + name + " not found", e);
+                } catch (MalformedURLException e) {
+                    throw new RemoteException("Invalid RMI name: " + name, e);
+                } catch (RemoteException e) {
+                    throw new RemoteException("Failed to look up the RMI resource "
+                            + name, e);
+                }
             }
-        } catch (NotBoundException e) {
-            throw new RepositoryException(
-                    "RMI resource " + name + " not found", e);
-        } catch (MalformedURLException e) {
-            throw new RepositoryException(
-                    "Invalid RMI name: " + name, e);
-        } catch (RemoteException e) {
-            throw new RepositoryException(
-                    "Failed to look up the RMI resource " + name, e);
-        }
+        };
     }
 
-    private Repository getRepository(RemoteRepository remote) {
-        return new ClientAdapterFactory().getRepository(remote);
-    }
+    /**
+     * Basic SafeClientRepository for the different lookup types of a rmi repository
+     */
+    private static class RmiSafeClientRepository extends SafeClientRepository {
 
+        public RmiSafeClientRepository(LocalAdapterFactory factory) {
+            super(factory);
+        }
+
+        @Override
+        protected RemoteRepository getRemoteRepository() throws RemoteException {
+            return null;
+        }
+
+    }
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/RemoteAdapterFactory.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/RemoteAdapterFactory.java
index ec9b1c7..301041a 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/RemoteAdapterFactory.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/RemoteAdapterFactory.java
@@ -380,7 +380,7 @@ public interface RemoteAdapterFactory {
     RemoteLockManager getRemoteLockManager(LockManager lockManager)
         throws RemoteException;
 
-    RemoteVersionManager getRemoteVersionManager(VersionManager versionManager)
+    RemoteVersionManager getRemoteVersionManager(Session session, VersionManager versionManager)
         throws RemoteException;
 
     /**
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerAdapterFactory.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerAdapterFactory.java
index 1a8c2af..f847acd 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerAdapterFactory.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerAdapterFactory.java
@@ -30,7 +30,6 @@ import javax.jcr.NodeIterator;
 import javax.jcr.Property;
 import javax.jcr.PropertyIterator;
 import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.Workspace;
 import javax.jcr.lock.Lock;
@@ -102,9 +101,9 @@ import org.apache.jackrabbit.rmi.server.principal.ServerPrincipal;
 import org.apache.jackrabbit.rmi.server.principal.ServerPrincipalIterator;
 import org.apache.jackrabbit.rmi.server.security.ServerAccessControlEntry;
 import org.apache.jackrabbit.rmi.server.security.ServerAccessControlList;
-import org.apache.jackrabbit.rmi.server.security.ServerAccessControlPolicyIterator;
 import org.apache.jackrabbit.rmi.server.security.ServerAccessControlManager;
 import org.apache.jackrabbit.rmi.server.security.ServerAccessControlPolicy;
+import org.apache.jackrabbit.rmi.server.security.ServerAccessControlPolicyIterator;
 import org.apache.jackrabbit.rmi.server.security.ServerPrivilege;
 
 /**
@@ -351,18 +350,13 @@ public class ServerAdapterFactory implements RemoteAdapterFactory {
             EventIterator events) throws RemoteException {
         RemoteEventCollection.RemoteEvent[] remoteEvents;
         if (events != null) {
-            List eventList = new ArrayList();
-            while (events.hasNext()) {
-                try {
-                    Event event = events.nextEvent();
-                    eventList.add(new ServerEventCollection.ServerEvent(
-                        event.getType(), event.getPath(), event.getUserID(),
-                        this));
-                } catch (RepositoryException re) {
-                    throw new RemoteException(re.getMessage(), re);
-                }
-            }
-            remoteEvents = (RemoteEventCollection.RemoteEvent[]) eventList.toArray(new RemoteEventCollection.RemoteEvent[eventList.size()]);
+			List<ServerEventCollection.ServerEvent> eventList = new ArrayList<ServerEventCollection.ServerEvent>();
+			while (events.hasNext()) {
+				Event event = events.nextEvent();
+				eventList
+						.add(new ServerEventCollection.ServerEvent(event, this));
+			}
+            remoteEvents = eventList.toArray(new RemoteEventCollection.RemoteEvent[eventList.size()]);
         } else {
             remoteEvents = new RemoteEventCollection.RemoteEvent[0]; // for
             // safety
@@ -450,9 +444,9 @@ public class ServerAdapterFactory implements RemoteAdapterFactory {
         return new ServerLockManager(lockManager, this);
     }
 
-    public RemoteVersionManager getRemoteVersionManager(
+    public RemoteVersionManager getRemoteVersionManager(Session session,
             VersionManager versionManager) throws RemoteException {
-        return new ServerVersionManager(versionManager, this);
+        return new ServerVersionManager(session, versionManager, this);
     }
 
     /**
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerEventCollection.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerEventCollection.java
index 2ad91a9..c242f62 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerEventCollection.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerEventCollection.java
@@ -17,6 +17,10 @@
 package org.apache.jackrabbit.rmi.server;
 
 import java.rmi.RemoteException;
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.observation.Event;
 
 import org.apache.jackrabbit.rmi.remote.RemoteEventCollection;
 
@@ -77,14 +81,8 @@ public class ServerEventCollection extends ServerObject implements
      */
     public static class ServerEvent extends ServerObject implements RemoteEvent {
 
-        /** Event type */
-        private final int type;
-
-        /** Item path */
-        private final String path;
-
-        /** User identifier */
-        private final String userID;
+        /** The adapted local event. */
+        private Event event;
 
         /**
          * Creates an instance of this class.
@@ -94,28 +92,46 @@ public class ServerEventCollection extends ServerObject implements
          * @param factory remote adapter factory
          * @throws RemoteException on RMI errors
          */
-        ServerEvent(
-                int type, String path, String userId, RemoteAdapterFactory factory)
+        ServerEvent(Event event, RemoteAdapterFactory factory)
                 throws RemoteException {
             super(factory);
-            this.type = type;
-            this.path = path;
-            this.userID = userId;
+            this.event = event;
         }
 
         /** {@inheritDoc} */
-        public String getPath() {
-            return path;
+        public String getPath() throws RepositoryException {
+            return event.getPath();
         }
 
         /** {@inheritDoc} */
         public int getType() {
-            return type;
+            return event.getType();
         }
 
         /** {@inheritDoc} */
         public String getUserID() {
-            return userID;
+            return event.getUserID();
         }
+
+        /** {@inheritDoc} */
+		public String getIdentifier() throws RepositoryException,
+				RemoteException {
+			return event.getIdentifier();
+		}
+
+        /** {@inheritDoc} */
+		public Map getInfo() throws RepositoryException, RemoteException {
+			return event.getInfo();
+		}
+
+        /** {@inheritDoc} */
+		public String getUserData() throws RepositoryException, RemoteException {
+			return event.getUserData();
+		}
+
+        /** {@inheritDoc} */
+		public long getDate() throws RepositoryException, RemoteException {
+			return event.getDate();
+		}
     }
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerLock.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerLock.java
index 7acfa4d..af268af 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerLock.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerLock.java
@@ -17,7 +17,6 @@
 package org.apache.jackrabbit.rmi.server;
 
 import java.rmi.RemoteException;
-import java.rmi.server.UnicastRemoteObject;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.lock.Lock;
@@ -85,4 +84,15 @@ public class ServerLock extends ServerObject implements RemoteLock {
     public boolean isSessionScoped() throws RemoteException {
         return lock.isSessionScoped();
     }
+
+    /** {@inheritDoc} */
+    public long getSecondsRemaining() throws RepositoryException, RemoteException {
+        return lock.getSecondsRemaining();
+    }
+
+    /** {@inheritDoc} */
+	public boolean isLockOwningSession() throws RemoteException {
+        return lock.isLockOwningSession();
+	}
+
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerNode.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerNode.java
index 237a0e9..b611d42 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerNode.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerNode.java
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.rmi.server;
 import java.rmi.RemoteException;
 
 import javax.jcr.Node;
+import javax.jcr.NodeIterator;
 import javax.jcr.Property;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
@@ -573,6 +574,85 @@ public class ServerNode extends ServerItem implements RemoteNode {
         }
     }
 
+    /** {@inheritDoc} */
+    public RemoteIterator getSharedSet() 
+    		throws RepositoryException, RemoteException {
+    	try {
+    		NodeIterator sharedSet = node.getSharedSet();
+    		return getFactory().getRemoteNodeIterator(sharedSet);
+    	} catch (RepositoryException ex) {
+    		throw getRepositoryException(ex);
+    	}
+    }
+
+    /** {@inheritDoc} */
+    public void followLifecycleTransition(String transition)
+    		throws RepositoryException, RemoteException {
+    	try {
+    		node.followLifecycleTransition(transition);
+    	} catch (RepositoryException ex) {
+    		throw getRepositoryException(ex);
+    	}
+    }
+
+    /** {@inheritDoc} */
+	public String[] getAllowedLifecycleTransistions()
+			throws RepositoryException, RemoteException {
+    	try {
+    		return node.getAllowedLifecycleTransistions();
+    	} catch (RepositoryException ex) {
+    		throw getRepositoryException(ex);
+    	}
+	}
+
+    /** {@inheritDoc} */
+	public RemoteIterator getWeakReferences() 
+			throws RepositoryException, RemoteException {
+    	try {
+    		return getFactory().getRemotePropertyIterator(node.getWeakReferences());
+    	} catch (RepositoryException ex) {
+    		throw getRepositoryException(ex);
+    	}
+	}
+
+    /** {@inheritDoc} */
+	public RemoteIterator getWeakReferences(String name)
+			throws RepositoryException, RemoteException {
+    	try {
+    		return getFactory().getRemotePropertyIterator(node.getWeakReferences(name));
+    	} catch (RepositoryException ex) {
+    		throw getRepositoryException(ex);
+    	}
+	}
+
+    /** {@inheritDoc} */
+	public void removeShare() throws RepositoryException, RemoteException {
+    	try {
+    		node.removeShare();
+    	} catch (RepositoryException ex) {
+    		throw getRepositoryException(ex);
+    	}
+	}
+
+    /** {@inheritDoc} */
+	public void removeSharedSet() throws RepositoryException, RemoteException {
+    	try {
+    		node.removeSharedSet();
+    	} catch (RepositoryException ex) {
+    		throw getRepositoryException(ex);
+    	}
+	}
+
+    /** {@inheritDoc} */
+	public void setPrimaryType(String nodeTypeName) 
+			throws RepositoryException, RemoteException {
+    	try {
+    		node.setPrimaryType(nodeTypeName);
+    	} catch (RepositoryException ex) {
+    		throw getRepositoryException(ex);
+    	}
+	}
+
     //---------- Implementation helper -----------------------------------------
 
     /**
@@ -602,4 +682,5 @@ public class ServerNode extends ServerItem implements RemoteNode {
         // otherwise fail
         throw new RepositoryException("Cannot find version " + versionUUID);
     }
+
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerNodeDefinition.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerNodeDefinition.java
index ec41f05..eab7236 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerNodeDefinition.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerNodeDefinition.java
@@ -72,4 +72,14 @@ public class ServerNodeDefinition extends ServerItemDefinition implements Remote
         return def.allowsSameNameSiblings();
     }
 
+    /** {@inheritDoc} */
+	public String getDefaultPrimaryTypeName() throws RemoteException {
+        return def.getDefaultPrimaryTypeName();
+	}
+
+    /** {@inheritDoc} */
+	public String[] getRequiredPrimaryTypeNames() throws RemoteException {
+        return def.getRequiredPrimaryTypeNames();
+	}
+
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerNodeType.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerNodeType.java
index 8ebc404..918d06f 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerNodeType.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerNodeType.java
@@ -23,6 +23,7 @@ import javax.jcr.nodetype.NodeDefinition;
 import javax.jcr.nodetype.NodeType;
 import javax.jcr.nodetype.PropertyDefinition;
 
+import org.apache.jackrabbit.rmi.remote.RemoteIterator;
 import org.apache.jackrabbit.rmi.remote.RemoteNodeDefinition;
 import org.apache.jackrabbit.rmi.remote.RemoteNodeType;
 import org.apache.jackrabbit.rmi.remote.RemotePropertyDefinition;
@@ -197,4 +198,34 @@ public class ServerNodeType extends ServerObject implements RemoteNodeType {
         return type.getPrimaryItemName();
     }
 
+    /** {@inheritDoc} */
+	public boolean canRemoveNode(String nodeName) {
+		return type.canRemoveNode(nodeName);
+	}
+
+    /** {@inheritDoc} */
+	public boolean canRemoveProperty(String propertyName) {
+		return type.canRemoveProperty(propertyName);
+	}
+
+    /** {@inheritDoc} */
+	public String[] getDeclaredSupertypeNames() {
+		return type.getDeclaredSupertypeNames();
+	}
+
+    /** {@inheritDoc} */
+	public boolean isQueryable() {
+		return type.isQueryable();
+	}
+
+    /** {@inheritDoc} */
+	public RemoteIterator getDeclaredSubtypes() throws RemoteException {
+       	return getFactory().getRemoteNodeTypeIterator(type.getDeclaredSubtypes());
+	}
+
+    /** {@inheritDoc} */
+	public RemoteIterator getSubtypes() throws RemoteException {
+		return getFactory().getRemoteNodeTypeIterator(type.getSubtypes());
+	}
+
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerObservationManager.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerObservationManager.java
index b4b1490..3819949 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerObservationManager.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerObservationManager.java
@@ -53,7 +53,7 @@ public class ServerObservationManager extends ServerObject implements
     /**
      * The map of event listener proxies indexed by the unique identifier.
      */
-    private Map proxyMap;
+	private Map<Long, ServerEventListenerProxy> proxyMap;
 
     /**
      * The queue to which event listener proxies post events to be reported
@@ -84,11 +84,11 @@ public class ServerObservationManager extends ServerObject implements
         ServerEventListenerProxy proxy;
         synchronized (this) {
             if (proxyMap == null) {
-                proxyMap = new HashMap();
+                proxyMap = new HashMap<Long, ServerEventListenerProxy>();
             }
 
-            Long id = new Long(listenerId);
-            proxy = (ServerEventListenerProxy) proxyMap.get(id);
+            Long id = Long.valueOf(listenerId);
+            proxy = proxyMap.get(id);
             if (proxy == null) {
                 proxy = new ServerEventListenerProxy(getFactory(), listenerId,
                     getQueue());
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerPropertyDefinition.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerPropertyDefinition.java
index e1b2fb7..42655a2 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerPropertyDefinition.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerPropertyDefinition.java
@@ -23,7 +23,6 @@ import javax.jcr.Value;
 import javax.jcr.nodetype.PropertyDefinition;
 
 import org.apache.jackrabbit.rmi.remote.RemotePropertyDefinition;
-import org.apache.jackrabbit.rmi.value.SerialValueFactory;
 
 /**
  * Remote adapter for the JCR
@@ -79,4 +78,19 @@ public class ServerPropertyDefinition extends ServerItemDefinition
         return def.isMultiple();
     }
 
+    /** {@inheritDoc} */
+	public String[] getAvailableQueryOperators() throws RemoteException {
+		return def.getAvailableQueryOperators();
+	}
+
+    /** {@inheritDoc} */
+	public boolean isFullTextSearchable() throws RemoteException {
+		return def.isFullTextSearchable();
+	}
+
+    /** {@inheritDoc} */
+	public boolean isQueryOrderable() throws RemoteException {
+		return def.isQueryOrderable();
+	}
+
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerQuery.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerQuery.java
index de22509..8aeec95 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerQuery.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerQuery.java
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.rmi.server;
 import java.rmi.RemoteException;
 
 import javax.jcr.RepositoryException;
+import javax.jcr.Value;
 import javax.jcr.query.Query;
 
 import org.apache.jackrabbit.rmi.remote.RemoteQuery;
@@ -80,4 +81,30 @@ public class ServerQuery extends ServerObject implements RemoteQuery {
         return getRemoteNode(query.storeAsNode(absPath));
     }
 
+    /** {@inheritDoc} */
+	public void bindValue(String varName, Value value)
+			throws RepositoryException, RemoteException {
+		query.bindValue(varName, value);
+	}
+
+    /** {@inheritDoc} */
+	public String[] getBindVariableNames() 
+			throws RepositoryException, RemoteException {
+        try {
+        	return query.getBindVariableNames();
+        } catch (RepositoryException ex) {
+            throw getRepositoryException(ex);
+        }
+	}
+
+    /** {@inheritDoc} */
+	public void setLimit(long limit) throws RemoteException {
+    	query.setLimit(limit);
+	}
+
+    /** {@inheritDoc} */
+	public void setOffset(long offset) throws RemoteException {
+		query.setOffset(offset);
+	}
+
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerQueryResult.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerQueryResult.java
index 1abd424..36535e9 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerQueryResult.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerQueryResult.java
@@ -68,4 +68,9 @@ public class ServerQueryResult extends ServerObject
         return getFactory().getRemoteNodeIterator(result.getNodes());
     }
 
+    /** {@inheritDoc} */
+	public String[] getSelectorNames() throws RepositoryException, RemoteException {
+		return result.getSelectorNames();
+	}
+
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerRepository.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerRepository.java
index 5f75ca6..75d7643 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerRepository.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerRepository.java
@@ -22,9 +22,11 @@ import javax.jcr.Credentials;
 import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
+import javax.jcr.Value;
 
 import org.apache.jackrabbit.rmi.remote.RemoteRepository;
 import org.apache.jackrabbit.rmi.remote.RemoteSession;
+import org.apache.jackrabbit.rmi.value.SerialValueFactory;
 
 /**
  * Remote adapter for the JCR {@link javax.jcr.Repository Repository}
@@ -108,4 +110,32 @@ public class ServerRepository extends ServerObject implements RemoteRepository {
         }
     }
 
+    /** {@inheritDoc} */
+	public Value getDescriptorValue(String key) throws RemoteException {
+    	try {
+            return SerialValueFactory.makeSerialValue(repository.getDescriptorValue(key));
+    	} catch (RepositoryException ex) {
+    		 throw new RemoteException(ex.getMessage(), ex);    		
+    	}
+	}
+
+    /** {@inheritDoc} */
+	public Value[] getDescriptorValues(String key) throws RemoteException {
+    	try {
+            return SerialValueFactory.makeSerialValueArray(repository.getDescriptorValues(key));
+    	} catch (RepositoryException ex) {
+    		throw new RemoteException(ex.getMessage(), ex);    		
+    	}
+	}
+
+    /** {@inheritDoc} */
+	public boolean isSingleValueDescriptor(String key) throws RemoteException {
+		return repository.isSingleValueDescriptor(key);
+	}
+
+    /** {@inheritDoc} */
+	public boolean isStandardDescriptor(String key) throws RemoteException {
+		return repository.isStandardDescriptor(key);
+	}
+
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerRow.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerRow.java
index b56135e..f14283f 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerRow.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerRow.java
@@ -22,6 +22,7 @@ import javax.jcr.RepositoryException;
 import javax.jcr.Value;
 import javax.jcr.query.Row;
 
+import org.apache.jackrabbit.rmi.remote.RemoteNode;
 import org.apache.jackrabbit.rmi.remote.RemoteRow;
 import org.apache.jackrabbit.rmi.value.SerialValueFactory;
 
@@ -54,12 +55,80 @@ public class ServerRow extends ServerObject implements RemoteRow {
 
     /** {@inheritDoc} */
     public Value[] getValues() throws RepositoryException, RemoteException {
-        return getSerialValues(row.getValues());
+    	try {
+    		return getSerialValues(row.getValues());
+    	} catch (RepositoryException ex) {
+    		throw getRepositoryException(ex);    		
+    	}
     }
 
     /** {@inheritDoc} */
     public Value getValue(String propertyName)
             throws RepositoryException, RemoteException {
-        return SerialValueFactory.makeSerialValue(row.getValue(propertyName));
+    	try {
+    		return SerialValueFactory.makeSerialValue(row.getValue(propertyName));
+    	} catch (RepositoryException ex) {
+    		 throw getRepositoryException(ex);    		
+    	}
     }
+
+    /** {@inheritDoc} */
+	public RemoteNode getNode() 
+			throws RepositoryException, RemoteException {
+    	try {
+    		return getRemoteNode(row.getNode());
+    	} catch (RepositoryException ex) {
+    		throw getRepositoryException(ex);    		
+    	}
+	}
+
+    /** {@inheritDoc} */
+	public RemoteNode getNode(String selectorName) 
+			throws RepositoryException, RemoteException {
+    	try {
+    		return getRemoteNode(row.getNode(selectorName));
+    	} catch (RepositoryException ex) {
+    		throw getRepositoryException(ex);    		
+    	}
+	}
+
+    /** {@inheritDoc} */
+	public String getPath() 
+			throws RepositoryException, RemoteException {
+    	try {
+    		return row.getPath();
+    	} catch (RepositoryException ex) {
+    		throw getRepositoryException(ex);    		
+    	}
+	}
+
+    /** {@inheritDoc} */
+	public String getPath(String selectorName) 
+			throws RepositoryException, RemoteException {
+    	try {
+    		return row.getPath(selectorName);
+    	} catch (RepositoryException ex) {
+    		throw getRepositoryException(ex);    		
+    	}
+	}
+
+    /** {@inheritDoc} */
+	public double getScore() 
+			throws RepositoryException, RemoteException {
+    	try {
+    		return row.getScore();
+    	} catch (RepositoryException ex) {
+    		throw getRepositoryException(ex);    		
+    	}
+	}
+
+    /** {@inheritDoc} */
+	public double getScore(String selectorName) 
+			throws RepositoryException, RemoteException {
+    	try {
+    		return row.getScore(selectorName);
+    	} catch (RepositoryException ex) {
+    		throw getRepositoryException(ex);    		
+    	}
+	}
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerVersion.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerVersion.java
index 0b313c9..581a9d0 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerVersion.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerVersion.java
@@ -22,6 +22,7 @@ import java.util.Calendar;
 import javax.jcr.RepositoryException;
 import javax.jcr.version.Version;
 
+import org.apache.jackrabbit.rmi.remote.RemoteNode;
 import org.apache.jackrabbit.rmi.remote.RemoteVersion;
 import org.apache.jackrabbit.rmi.remote.RemoteVersionHistory;
 
@@ -76,15 +77,14 @@ public class ServerVersion extends ServerNode implements RemoteVersion {
         }
     }
 
-//  This is only available after 0.16.2
-//    /** {@inheritDoc} */
-//    public RemoteVersionHistory getContainingHistory() throws RepositoryException {
-//        try {
-//            return getFactory().getRemoteVersionHistory(version.getContainingHistory());
-//        } catch (RepositoryException ex) {
-//            throw getRepositoryException(ex);
-//        }
-//    }
+    /** {@inheritDoc} */
+    public RemoteVersionHistory getContainingHistory() throws RepositoryException, RemoteException {
+        try {
+            return getFactory().getRemoteVersionHistory(version.getContainingHistory());
+        } catch (RepositoryException ex) {
+            throw getRepositoryException(ex);
+        }
+    }
 
     /** {@inheritDoc} */
     public Calendar getCreated() throws RepositoryException {
@@ -96,6 +96,21 @@ public class ServerVersion extends ServerNode implements RemoteVersion {
     }
 
     /** {@inheritDoc} */
+    public RemoteVersion getLinearSuccessor() throws RepositoryException,
+    		RemoteException {
+        try {
+            Version linearSuccessor = version.getLinearSuccessor();
+            if (linearSuccessor == null) {
+                return null;
+            } else {
+                return getFactory().getRemoteVersion(linearSuccessor);
+            }
+        } catch (RepositoryException ex) {
+            throw getRepositoryException(ex);
+        }
+    }
+    
+    /** {@inheritDoc} */
     public RemoteVersion[] getSuccessors() throws RepositoryException, RemoteException {
         try {
             return getRemoteVersionArray(version.getSuccessors());
@@ -105,6 +120,21 @@ public class ServerVersion extends ServerNode implements RemoteVersion {
     }
 
     /** {@inheritDoc} */
+    public RemoteVersion getLinearPredecessor() throws RepositoryException,
+    		RemoteException {
+        try {
+            Version linearPredecessor = version.getLinearPredecessor();
+            if (linearPredecessor == null) {
+                return null;
+            } else {
+                return getFactory().getRemoteVersion(linearPredecessor);
+            }
+        } catch (RepositoryException ex) {
+            throw getRepositoryException(ex);
+        }
+    }
+    
+    /** {@inheritDoc} */
     public RemoteVersion[] getPredecessors() throws RepositoryException, RemoteException {
         try {
             return getRemoteVersionArray(version.getPredecessors());
@@ -114,9 +144,10 @@ public class ServerVersion extends ServerNode implements RemoteVersion {
     }
 
     /** {@inheritDoc} */
-    public RemoteVersionHistory getContainingHistory() throws RepositoryException, RemoteException {
+    public RemoteNode getFrozenNode() throws RepositoryException,
+    		RemoteException {
         try {
-            return getFactory().getRemoteVersionHistory(version.getContainingHistory());
+            return getFactory().getRemoteNode(version.getFrozenNode());
         } catch (RepositoryException ex) {
             throw getRepositoryException(ex);
         }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerVersionHistory.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerVersionHistory.java
index 5b77fd9..466a496 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerVersionHistory.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerVersionHistory.java
@@ -56,6 +56,16 @@ public class ServerVersionHistory extends ServerNode
     }
 
     /** {@inheritDoc} */
+    public String getVersionableIdentifier() throws RepositoryException,
+    		RemoteException {
+        try {
+            return versionHistory.getVersionableIdentifier();
+        } catch (RepositoryException ex) {
+            throw getRepositoryException(ex);
+        }
+    }
+    
+    /** {@inheritDoc} */
     public RemoteVersion getRootVersion()
             throws RepositoryException, RemoteException {
         try {
@@ -66,6 +76,17 @@ public class ServerVersionHistory extends ServerNode
     }
 
     /** {@inheritDoc} */
+    public RemoteIterator getAllLinearVersions() throws RepositoryException,
+    		RemoteException {
+        try {
+            return getFactory().getRemoteVersionIterator(
+                    versionHistory.getAllLinearVersions());
+        } catch (RepositoryException ex) {
+            throw getRepositoryException(ex);
+        }
+    }
+    
+    /** {@inheritDoc} */
     public RemoteIterator getAllVersions()
             throws RepositoryException, RemoteException {
         try {
@@ -77,6 +98,28 @@ public class ServerVersionHistory extends ServerNode
     }
 
     /** {@inheritDoc} */
+    public RemoteIterator getAllLinearFrozenNodes() throws RepositoryException,
+    		RemoteException {
+        try {
+            return getFactory().getRemoteNodeIterator(
+                    versionHistory.getAllLinearFrozenNodes());
+        } catch (RepositoryException ex) {
+            throw getRepositoryException(ex);
+        }
+    }
+    
+    /** {@inheritDoc} */
+    public RemoteIterator getAllFrozenNodes() throws RepositoryException,
+    		RemoteException {
+        try {
+            return getFactory().getRemoteNodeIterator(
+                    versionHistory.getAllFrozenNodes());
+        } catch (RepositoryException ex) {
+            throw getRepositoryException(ex);
+        }
+    }
+    
+    /** {@inheritDoc} */
     public RemoteVersion getVersion(String versionName)
         throws RepositoryException, RemoteException {
         try {
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerVersionManager.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerVersionManager.java
index e4968a5..d8766af 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerVersionManager.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerVersionManager.java
@@ -18,7 +18,10 @@ package org.apache.jackrabbit.rmi.server;
 
 import java.rmi.RemoteException;
 
+import javax.jcr.Node;
 import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.version.Version;
 import javax.jcr.version.VersionManager;
 
 import org.apache.jackrabbit.rmi.remote.RemoteIterator;
@@ -30,12 +33,15 @@ import org.apache.jackrabbit.rmi.remote.RemoteVersionManager;
 public class ServerVersionManager extends ServerObject
         implements RemoteVersionManager {
 
+    private final Session session;    
+    
     private final VersionManager manager;
-
-    public ServerVersionManager(
+    
+    public ServerVersionManager(Session session,
             VersionManager manager, RemoteAdapterFactory factory)
             throws RemoteException {
         super(factory);
+        this.session = session;
         this.manager = manager;
     }
 
@@ -87,7 +93,12 @@ public class ServerVersionManager extends ServerObject
     public RemoteNode getActivity()
             throws RepositoryException, RemoteException {
         try {
-            return getFactory().getRemoteNode(manager.getActivity());
+            Node activity = manager.getActivity();
+            if (activity == null) {
+                return null;
+            } else {
+                return getFactory().getRemoteNode(activity);
+            }
         } catch (RepositoryException e) {
             throw getRepositoryException(e);
         }
@@ -164,4 +175,104 @@ public class ServerVersionManager extends ServerObject
         }
     }
 
+    public void cancelMerge(String absPath, String versionIdentifier)
+            throws RepositoryException, RemoteException {
+        try {
+            Version version = (Version) session.getNodeByIdentifier(versionIdentifier);
+            manager.cancelMerge(absPath, version);
+        } catch (RepositoryException e) {
+            throw getRepositoryException(e);
+        }
+    }
+
+    public void doneMerge(String absPath, String versionIdentifier)
+            throws RepositoryException, RemoteException {
+        try {
+            Version version = (Version) session.getNodeByIdentifier(versionIdentifier);
+            manager.doneMerge(absPath, version);
+        } catch (RepositoryException e) {
+            throw getRepositoryException(e);
+        }
+    }
+
+    @Override
+    public void restore(String[] versionIdentifiers, boolean removeExisting)
+            throws RepositoryException, RemoteException {
+        try {
+            Version[] versions = new Version[versionIdentifiers.length];
+            for (int i = 0; i < versions.length; i++) {
+                Version version = (Version) session.getNodeByIdentifier(versionIdentifiers[i]);
+                versions[i] = version;
+            }
+            manager.restore(versions, removeExisting);
+        } catch (RepositoryException e) {
+            throw getRepositoryException(e);
+        }
+    }
+
+    @Override
+    public void restore(String versionIdentifier, boolean removeExisting)
+            throws RepositoryException, RemoteException {
+        try {
+            Version version = (Version) session.getNodeByIdentifier(versionIdentifier);
+            manager.restore(version, removeExisting);
+        } catch (RepositoryException e) {
+            throw getRepositoryException(e);
+        }
+    }
+
+    @Override
+    public RemoteNode setActivity(String activityNodeIdentifier)
+            throws RepositoryException, RemoteException {
+        try {
+            Node newActivityNode;
+            if (activityNodeIdentifier == null) {
+                newActivityNode = null;
+            } else {
+                newActivityNode = session.getNodeByIdentifier(activityNodeIdentifier);
+            }
+            Node oldActivityNode = manager.setActivity(newActivityNode);
+            if (oldActivityNode == null) {
+                return null;
+            } else {
+                return getFactory().getRemoteNode(oldActivityNode);
+            }
+        } catch (RepositoryException e) {
+            throw getRepositoryException(e);
+        }
+    }
+
+    @Override
+    public void removeActivity(String activityNodeIdentifier)
+            throws RepositoryException, RemoteException {
+        try {
+            Node activityNode = session.getNodeByIdentifier(activityNodeIdentifier);
+            manager.removeActivity(activityNode);
+        } catch (RepositoryException e) {
+            throw getRepositoryException(e);
+        }
+    }
+
+    @Override
+    public RemoteIterator merge(String activityNodeIdentifier)
+            throws RepositoryException, RemoteException {
+        try {
+            Node activityNode = session.getNodeByIdentifier(activityNodeIdentifier);
+            return getFactory().getRemoteNodeIterator(manager.merge(activityNode));
+        } catch (RepositoryException e) {
+            throw getRepositoryException(e);
+        }
+    }
+
+    @Override
+    public void restoreVI(String absPath, String versionIdentifier,
+            boolean removeExisting) throws RepositoryException, RemoteException {
+        try {
+            Version version = (Version) session.getNodeByIdentifier(versionIdentifier);
+            manager.restore(absPath, version, removeExisting);
+        } catch (RepositoryException e) {
+            throw getRepositoryException(e);
+        }
+    }
+
 }
diff --git a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerWorkspace.java b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerWorkspace.java
index 87256c8..664adba 100644
--- a/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerWorkspace.java
+++ b/jackrabbit-jcr-rmi/src/main/java/org/apache/jackrabbit/rmi/server/ServerWorkspace.java
@@ -229,7 +229,7 @@ public class ServerWorkspace extends ServerObject implements RemoteWorkspace {
             if (remoteVersionManager == null) {
                 VersionManager versionManager = workspace.getVersionManager();
                 remoteVersionManager =
-                    getFactory().getRemoteVersionManager(versionManager);
+                    getFactory().getRemoteVersionManager(workspace.getSession(), versionManager);
             }
             return remoteVersionManager;
         } catch (RepositoryException ex) {
diff --git a/jackrabbit-jcr-rmi/src/main/javadoc/org/apache/jackrabbit/rmi/observation/package.html b/jackrabbit-jcr-rmi/src/main/javadoc/org/apache/jackrabbit/rmi/observation/package.html
index b09f026..c526200 100644
--- a/jackrabbit-jcr-rmi/src/main/javadoc/org/apache/jackrabbit/rmi/observation/package.html
+++ b/jackrabbit-jcr-rmi/src/main/javadoc/org/apache/jackrabbit/rmi/observation/package.html
@@ -80,4 +80,4 @@ which works in the background asking the server for the RemoteEvents from
 the event queue. Each such event is then dispatched to the client-side
 event listener by calling the EventListener.onEvent() method.
 
-</body
+</body>
diff --git a/jackrabbit-jcr-rmi/src/test/java/org/apache/jackrabbit/rmi/RepositoryStubImpl.java b/jackrabbit-jcr-rmi/src/test/java/org/apache/jackrabbit/rmi/RepositoryStubImpl.java
index 5dd09a4..d468168 100644
--- a/jackrabbit-jcr-rmi/src/test/java/org/apache/jackrabbit/rmi/RepositoryStubImpl.java
+++ b/jackrabbit-jcr-rmi/src/test/java/org/apache/jackrabbit/rmi/RepositoryStubImpl.java
@@ -77,7 +77,7 @@ public class RepositoryStubImpl extends JackrabbitRepositoryStub {
                 repository =
                     laf.getRepository((RemoteRepository) ois.readObject());
             } catch (Exception e) {
-                throw new RepositoryStubException(e.getMessage());
+                throw new RepositoryStubException(e);
             }
         }
         return repository;
diff --git a/jackrabbit-jcr-server/pom.xml b/jackrabbit-jcr-server/pom.xml
index 17cf9da..e1bf7a6 100644
--- a/jackrabbit-jcr-server/pom.xml
+++ b/jackrabbit-jcr-server/pom.xml
@@ -26,7 +26,7 @@
   <parent>
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>jackrabbit-parent</artifactId>
-    <version>2.3.6</version>
+    <version>2.10.1</version>
     <relativePath>../jackrabbit-parent/pom.xml</relativePath>
   </parent>
   <artifactId>jackrabbit-jcr-server</artifactId>
@@ -44,12 +44,18 @@
           </includes>
           <forkMode>once</forkMode>
           <argLine>-Xmx128m -enableassertions</argLine>
+          <systemProperties>
+            <property>
+              <name>derby.stream.error.file</name>
+              <value>target/derby.log</value>
+            </property>
+          </systemProperties>
         </configuration>
       </plugin>
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-scr-plugin</artifactId>
-        <version>1.7.2</version>
+        <version>1.12.0</version>
         <executions>
             <execution>
               <id>generate-scr-scrdescriptor</id>
@@ -63,6 +69,20 @@
             </configuration>
           </execution>
         </executions>
+        <dependencies>
+          <!-- See https://issues.apache.org/jira/browse/FELIX-2492 -->
+          <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>${slf4j.api.version}</version>
+          </dependency>
+          <!-- See http://stackoverflow.com/questions/13269942/querydsl-maven-plugin-missing-a-class -->
+          <dependency>
+            <groupId>org.codehaus.plexus</groupId>
+            <artifactId>plexus-utils</artifactId>
+            <version>3.0.10</version>
+          </dependency>
+        </dependencies>
       </plugin>
       <plugin>
         <groupId>org.apache.felix</groupId>
@@ -71,13 +91,8 @@
         <configuration>
           <instructions>
             <Export-Package>
-              !*
+              org.apache.jackrabbit.server
             </Export-Package>
-            <Private-Package>
-              org.apache.jackrabbit.server.*,
-              org.apache.jackrabbit.webdav.jcr.*,
-              org.apache.jackrabbit.webdav.simple
-            </Private-Package>
           </instructions>
         </configuration>
       </plugin>
@@ -165,17 +180,17 @@
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-spi-commons</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-commons</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-webdav</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.tika</groupId>
@@ -205,10 +220,15 @@
     <dependency>
       <groupId>org.apache.felix</groupId>
       <artifactId>org.apache.felix.scr.annotations</artifactId>
-      <version>1.6.0</version>
+      <version>1.9.2</version>
       <scope>provided</scope>
     </dependency>
-    
+    <dependency>
+      <groupId>biz.aQute</groupId>
+      <artifactId>bndlib</artifactId>
+      <scope>provided</scope>
+    </dependency>
+
     <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
@@ -217,7 +237,13 @@
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-core</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jackrabbit</groupId>
+      <artifactId>jackrabbit-jcr-tests</artifactId>
+      <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
     <dependency>
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/BasicCredentialsProvider.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/BasicCredentialsProvider.java
index b666123..f4e9afb 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/BasicCredentialsProvider.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/BasicCredentialsProvider.java
@@ -66,7 +66,7 @@ public class BasicCredentialsProvider implements CredentialsProvider {
      * <li> if this field has a 'user:password' value, the respective
      *      simple credentials are generated.
      * </ul>
-     * <p/>
+     * <p>
      * If the request header is present but cannot be parsed a
      * <code>ServletException</code> is thrown.
      *
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/SessionProviderImpl.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/SessionProviderImpl.java
index f139651..52054af 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/SessionProviderImpl.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/SessionProviderImpl.java
@@ -16,9 +16,9 @@
  */
 package org.apache.jackrabbit.server;
 
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Locale;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 
 import javax.jcr.Credentials;
 import javax.jcr.LoginException;
@@ -28,24 +28,31 @@ import javax.jcr.Session;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 
-import org.apache.jackrabbit.commons.webdav.JcrRemotingConstants;
-import org.apache.jackrabbit.spi.commons.SessionExtensions;
-import org.apache.jackrabbit.util.Text;
-import org.apache.jackrabbit.webdav.util.LinkHeaderFieldParser;
-
 /**
- * This Class implements a default session provider uses a credentials provider.
+ * This class implements a default session provider based on a given
+ * {@link CredentialsProvider credentials provider}. Additionally,
+ * since Jackrabbit 2.4, if another session provider is available as
+ * the "org.apache.jackrabbit.server.SessionProvider" request attribute,
+ * then that provider is asked first for a session before the default
+ * credential-based login mechanism is used.
  */
 public class SessionProviderImpl implements SessionProvider {
 
-    public static final String ATTRIBUTE_SESSION_ID = SessionProviderImpl.class + "#sessionid()";
-    
     /**
      * the credentials provider
      */
     private CredentialsProvider cp;
 
     /**
+     * Map of sessions acquired from custom session providers looked up
+     * from request attributes. We need to keep track of such providers
+     * so we can route the {@link #releaseSession(Session)} call to the
+     * correct provider.
+     */
+    private final Map<Session, SessionProvider> externalSessions =
+            Collections.synchronizedMap(new HashMap<Session, SessionProvider>());
+
+    /**
      * Creates a new SessionProvider
      * 
      * @param cp
@@ -60,25 +67,28 @@ public class SessionProviderImpl implements SessionProvider {
     public Session getSession(HttpServletRequest request,
             Repository repository, String workspace) throws LoginException,
             RepositoryException, ServletException {
-        Credentials creds = cp.getCredentials(request);
-        Session s;
-        if (creds == null) {
-            s = repository.login(workspace);
-        } else {
-            s = repository.login(creds, workspace);
+        Session s = null;
+
+        // JCR-3222: Check if a custom session provider is available as a
+        // request attribute. If one is available, ask it first for a session.
+        Object object = request.getAttribute(SessionProvider.class.getName());
+        if (object instanceof SessionProvider) {
+            SessionProvider provider = (SessionProvider) object;
+            s = provider.getSession(request, repository, workspace);
+            if (s != null) {
+                externalSessions.put(s, provider);
+            }
         }
 
-        // extract information from Link header fields
-        LinkHeaderFieldParser lhfp = new LinkHeaderFieldParser(
-                request.getHeaders("Link"));
-        String userData = getJcrUserData(lhfp);
-        s.getWorkspace().getObservationManager().setUserData(userData);
-
-        String sessionId = getSessionIdentifier(lhfp);
-        if (s instanceof SessionExtensions) {
-            SessionExtensions xs = (SessionExtensions) s;
-            xs.setAttribute(ATTRIBUTE_SESSION_ID, sessionId);
+        if (s == null) {
+            Credentials creds = cp.getCredentials(request);
+            if (creds == null) {
+                s = repository.login(workspace);
+            } else {
+                s = repository.login(creds, workspace);
+            }
         }
+
         return s;
     }
 
@@ -86,49 +96,14 @@ public class SessionProviderImpl implements SessionProvider {
      * {@inheritDoc }
      */
     public void releaseSession(Session session) {
-        session.logout();
-    }
-
-    // find first link relation for JCR User Data
-    private String getJcrUserData(LinkHeaderFieldParser lhfp) {
-        String jcrUserData = null;
-        String target = lhfp
-                .getFirstTargetForRelation(JcrRemotingConstants.RELATION_USER_DATA);
-        if (target != null) {
-            jcrUserData = getJcrUserData(target);
+        // JCR-3222: If the session was acquired from a custom session
+        // provider, we need to ask that provider to release the session.
+        SessionProvider provider = externalSessions.remove(session);
+        if (provider != null) {
+            provider.releaseSession(session);
+        } else {
+            session.logout();
         }
-
-        return jcrUserData;
-    }
-
-    // find first link relation for remote session identifier
-    private String getSessionIdentifier(LinkHeaderFieldParser lhfp) {
-        return lhfp
-                .getFirstTargetForRelation(JcrRemotingConstants.RELATION_REMOTE_SESSION_ID);
     }
 
-    // extracts User Data string from RFC 2397 "data" URI
-    // only supports the simple case of "data:,..." for now
-    private String getJcrUserData(String target) {
-        try {
-            URI datauri = new URI(target);
-
-            String scheme = datauri.getScheme();
-
-            // Poor Man's data: URI parsing
-            if (scheme != null
-                    && "data".equals(scheme.toLowerCase(Locale.ENGLISH))) {
-
-                String sspart = datauri.getRawSchemeSpecificPart();
-
-                if (sspart.startsWith(",")) {
-                    return Text.unescape(sspart.substring(1));
-                }
-            }
-        } catch (URISyntaxException ex) {
-            // not a URI, skip
-        }
-
-        return null;
-    }
 }
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/io/DefaultHandler.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/io/DefaultHandler.java
index 96f3040..af7da3e 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/io/DefaultHandler.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/io/DefaultHandler.java
@@ -63,7 +63,7 @@ import java.util.HashMap;
  * {@link JcrConstants#JCR_CONTENT jcr:content} and the nodetype specified
  * by {@link #getContentNodeType()}.</li>
  * </ul>
- * <p/>
+ * <p>
  * Import of the content:<br>
  * The content is imported to the {@link JcrConstants#JCR_DATA} property of the
  * content node. By default this handler will fail on a attempt to create/replace
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/io/DirListingExportHandler.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/io/DirListingExportHandler.java
index 20f0363..dc3516b 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/io/DirListingExportHandler.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/io/DirListingExportHandler.java
@@ -167,7 +167,7 @@ public class DirListingExportHandler implements IOHandler, PropertyHandler {
                             writer.print("/");
                         }
                         writer.print("\">");
-                        writer.print(label);
+                        writer.print(Text.encodeIllegalXMLCharacters(label));
                         writer.print("</a></li>");
                     }
                 }
@@ -227,7 +227,7 @@ public class DirListingExportHandler implements IOHandler, PropertyHandler {
                     writer.print("<li><a href=\"");
                     writer.print(child.getHref());
                     writer.print("\">");
-                    writer.print(label);
+                    writer.print(Text.encodeIllegalXMLCharacters(label));
                     writer.print("</a></li>");
                 }
                 writer.print("</ul><hr size=\"1\"><em>Powered by <a href=\"");
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/io/PropertyHandler.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/io/PropertyHandler.java
index aec4719..c707c19 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/io/PropertyHandler.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/io/PropertyHandler.java
@@ -47,7 +47,7 @@ public interface PropertyHandler {
      * @param isCollection
      * @return true if the export succeeded.
      * @throws RepositoryException If an attempt is made to export properties
-     * even if {@link PropertyHandler#canExport{PropertyExportContext, boolean)}
+     * even if {@link PropertyHandler#canExport(PropertyExportContext, boolean)}
      * returns false or if some other unrecoverable error occurs.
      */
     public boolean exportProperties(PropertyExportContext exportContext, boolean isCollection) throws RepositoryException;
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/jcr/JCRWebdavServer.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/jcr/JCRWebdavServer.java
index cd36a3f..461b581 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/jcr/JCRWebdavServer.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/jcr/JCRWebdavServer.java
@@ -16,7 +16,10 @@
  */
 package org.apache.jackrabbit.server.jcr;
 
+import org.apache.jackrabbit.commons.webdav.JcrRemotingConstants;
 import org.apache.jackrabbit.server.SessionProvider;
+import org.apache.jackrabbit.spi.commons.SessionExtensions;
+import org.apache.jackrabbit.util.Text;
 import org.apache.jackrabbit.webdav.DavException;
 import org.apache.jackrabbit.webdav.DavMethods;
 import org.apache.jackrabbit.webdav.DavSession;
@@ -25,6 +28,7 @@ import org.apache.jackrabbit.webdav.WebdavRequest;
 import org.apache.jackrabbit.webdav.header.IfHeader;
 import org.apache.jackrabbit.webdav.jcr.JcrDavException;
 import org.apache.jackrabbit.webdav.jcr.JcrDavSession;
+import org.apache.jackrabbit.webdav.util.LinkHeaderFieldParser;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -32,8 +36,12 @@ import javax.jcr.LoginException;
 import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletResponse;
+
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
@@ -325,7 +333,17 @@ public class JCRWebdavServer implements DavSessionProvider {
                 if (DavMethods.DAV_MKWORKSPACE != DavMethods.getMethodCode(request.getMethod())) {
                     workspaceName = request.getRequestLocator().getWorkspaceName();
                 }
-                return sessionProvider.getSession(request, repository, workspaceName);
+
+                Session session = sessionProvider.getSession(
+                        request, repository, workspaceName);
+
+                // extract information from Link header fields
+                LinkHeaderFieldParser lhfp =
+                        new LinkHeaderFieldParser(request.getHeaders("Link"));
+                setJcrUserData(session, lhfp);
+                setSessionIdentifier(session, lhfp);
+
+                return session;
             } catch (LoginException e) {
                 // LoginException results in UNAUTHORIZED,
                 throw new JcrDavException(e);
@@ -337,6 +355,53 @@ public class JCRWebdavServer implements DavSessionProvider {
             }
         }
 
+        /**
+         * Find first link relation for JCR user data and set it as
+         * the user data of the observation manager of the given session.
+         */
+        private void setJcrUserData(
+                Session session, LinkHeaderFieldParser lhfp)
+                throws RepositoryException {
+            String data = null;
+
+            // extract User Data string from RFC 2397 "data" URI
+            // only supports the simple case of "data:,..." for now
+            String target = lhfp.getFirstTargetForRelation(
+                    JcrRemotingConstants.RELATION_USER_DATA);
+            if (target != null) {
+                try {
+                    URI uri = new URI(target);
+                    // Poor Man's data: URI parsing
+                    if ("data".equalsIgnoreCase(uri.getScheme())) {
+                        String sspart = uri.getRawSchemeSpecificPart();
+                        if (sspart.startsWith(",")) {
+                            data = Text.unescape(sspart.substring(1));
+                        }
+                    }
+                } catch (URISyntaxException ex) {
+                    // not a URI, skip
+                }
+            }
+
+            try {
+                session.getWorkspace().getObservationManager().setUserData(data);
+            } catch (UnsupportedRepositoryOperationException ignore) {
+            }
+        }
+
+        /**
+         * Find first link relation for remote session identifier and set
+         * it as an attribute of the given session.
+         */
+        private void setSessionIdentifier(
+                Session session, LinkHeaderFieldParser lhfp) {
+            if (session instanceof SessionExtensions) {
+                String name = JcrRemotingConstants.RELATION_REMOTE_SESSION_ID;
+                String id = lhfp.getFirstTargetForRelation(name);
+                ((SessionExtensions) session).setAttribute(name, id);
+            }
+        }
+
         private String getUserID(DavSession session) {
             try {
                 Session s = DavSessionImpl.getRepositorySession(session);
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/package-info.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/package-info.java
new file mode 100644
index 0000000..079ae48
--- /dev/null
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ at aQute.bnd.annotation.Version("1.0")
+package org.apache.jackrabbit.server;
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/AclRemoveHandler.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/AclRemoveHandler.java
new file mode 100644
index 0000000..9f508b5
--- /dev/null
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/AclRemoveHandler.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.server.remoting.davex;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+
+import org.apache.jackrabbit.util.Text;
+
+public class AclRemoveHandler implements ProtectedItemRemoveHandler {
+
+    private static final String NT_REP_ACL = "rep:ACL";
+
+    @Override
+    public boolean remove(Session session, String itemPath) throws RepositoryException {
+        if (canHandle(session, itemPath)) {
+            String controlledPath = Text.getRelativeParent(itemPath, 1);
+            AccessControlManager acMgr = session.getAccessControlManager();
+            AccessControlPolicy[] policies = acMgr.getPolicies(controlledPath);
+            for (AccessControlPolicy policy : policies) {
+                acMgr.removePolicy(controlledPath, policy);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    // -----------------------------------------------------------< private >---
+    private boolean canHandle(Session session, String itemPath) throws RepositoryException {
+        Item aclItem = session.getItem(itemPath);
+        if (aclItem.isNode() && itemPath.startsWith("/")) {
+            if (isJackrabbitAclNodeType((Node) aclItem)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isJackrabbitAclNodeType(Node aclNode) throws RepositoryException {
+        String ntName = aclNode.getPrimaryNodeType().getName();
+        return ntName.equals(NT_REP_ACL);
+    }
+}
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DavexServletService.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DavexServletService.java
index 3861e72..7c6b35b 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DavexServletService.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/DavexServletService.java
@@ -18,17 +18,29 @@
  */
 package org.apache.jackrabbit.server.remoting.davex;
 
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Set;
 
+import javax.jcr.LoginException;
 import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
 import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
 
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Properties;
 import org.apache.felix.scr.annotations.Property;
 import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
 import org.apache.felix.scr.annotations.Service;
+import org.apache.jackrabbit.server.SessionProvider;
 import org.apache.jackrabbit.webdav.server.AbstractWebdavServlet;
 import org.apache.jackrabbit.webdav.util.CSRFUtil;
 
@@ -38,8 +50,15 @@ import org.apache.jackrabbit.webdav.util.CSRFUtil;
     @Property(name = "service.description", value = "Apache Jackrabbit JcrRemoting Servlet"),
     @Property(name = JcrRemotingServlet.INIT_PARAM_AUTHENTICATE_HEADER, value = AbstractWebdavServlet.DEFAULT_AUTHENTICATE_HEADER),
     @Property(name = JcrRemotingServlet.INIT_PARAM_CSRF_PROTECTION, value = CSRFUtil.DISABLED),
-    @Property(name = JcrRemotingServlet.INIT_PARAM_MISSING_AUTH_MAPPING, value = "") })
-public class DavexServletService extends JcrRemotingServlet {
+    @Property(name = JcrRemotingServlet.INIT_PARAM_MISSING_AUTH_MAPPING, value = ""),
+    @Property(name = "contextId", value = "") })
+ at Reference(
+        name = "providers", referenceInterface = SessionProvider.class,
+        policy = ReferencePolicy.DYNAMIC,
+        cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
+        bind = "addSessionProvider", unbind = "removeSessionProvider")
+public class DavexServletService extends JcrRemotingServlet
+        implements SessionProvider {
 
     /** Serial version UID */
     private static final long serialVersionUID = -901601294536148635L;
@@ -54,6 +73,22 @@ public class DavexServletService extends JcrRemotingServlet {
 
     private String alias;
 
+    /**
+     * Currently available custom session providers. They're used
+     * first before the default provider gets consulted. The associated
+     * set of sessions is used to forcibly release all sessions acquired
+     * from a provider when that provider is being removed.
+     */
+    private final Map<SessionProvider, Set<Session>> providers =
+            new LinkedHashMap<SessionProvider, Set<Session>>();
+
+    /**
+     * Currently active sessions. Used to link a session to the correct
+     * provider in the {@link #releaseSession(Session)} method.
+     */
+    private final Map<Session, SessionProvider> sessions =
+            new HashMap<Session, SessionProvider>();
+
     @Override
     protected Repository getRepository() {
         return repository;
@@ -78,4 +113,78 @@ public class DavexServletService extends JcrRemotingServlet {
         }
     }
 
+    @Override
+    protected SessionProvider getSessionProvider() {
+        return this;
+    }
+
+    /**
+     * Adds a custom session provider service.
+     *
+     * @param provider session provider
+     */
+    public synchronized void addSessionProvider(SessionProvider provider) {
+        providers.put(provider, new HashSet<Session>());
+    }
+
+    /**
+     * Removes a custom session provider service. All active sessions
+     * acquired from that provider are forcibly released.
+     *
+     * @param provider session provider
+     */
+    public synchronized void removeSessionProvider(SessionProvider provider) {
+        Set<Session> sessions = providers.remove(provider);
+        if (sessions != null) {
+            for (Session session : sessions) {
+                releaseSession(session);
+            }
+        }
+    }
+
+    //-----------------------------------------------------< SessionProvider >
+
+    /**
+     * Asks each available session provider in order for a session and
+     * returns the first session given. The default provider is used
+     * if no custom provider service is available or can provide a requested
+     * session.
+     */
+    public synchronized Session getSession(
+            HttpServletRequest request, Repository repository, String workspace)
+            throws LoginException, ServletException, RepositoryException {
+        SessionProvider provider = null;
+        Session session = null;
+
+        for (Map.Entry<SessionProvider, Set<Session>> entry : providers.entrySet()) {
+            provider = entry.getKey();
+            session = provider.getSession(request, repository, workspace);
+            if (session != null) {
+                entry.getValue().add(session);
+                break;
+            }
+        }
+
+        if (session == null) {
+            provider = super.getSessionProvider();
+            session = provider.getSession(request, repository, workspace);
+        }
+
+        if (session != null) {
+            sessions.put(session, provider);
+        }
+
+        return session;
+    }
+
+    /**
+     * Releases the given session using the provider from which it was acquired.
+     */
+    public synchronized void releaseSession(Session session) {
+        SessionProvider provider = sessions.remove(session);
+        if (provider != null) {
+            provider.releaseSession(session);
+        }
+    }
+
 }
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JcrRemotingServlet.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JcrRemotingServlet.java
index 175f24d..308b69e 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JcrRemotingServlet.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JcrRemotingServlet.java
@@ -78,12 +78,12 @@ import org.slf4j.LoggerFactory;
  * Upon RepositoryService.getItemInfos a JSON object is composed containing
  * the information for the requested node and its child items up to a
  * specified or configuration determined depth.
- * <p/>
+ * <p>
  * Batch read is triggered by adding a '.json' extension to the resource href.
  * Optionally the client may explicitly specify the desired batch read depth
  * by appending '.depth.json' extension. If no json extension is present the
  * GET request is processed by the base servlet.
- * <p/>
+ * <p>
  * The JSON writer applies the following rules:
  * 
  * <pre>
@@ -235,12 +235,18 @@ public abstract class JcrRemotingServlet extends JCRWebdavServerServlet {
      */
     public static final String INIT_PARAM_BATCHREAD_CONFIG = "batchread-config";
 
+    /**
+     * the 'protectedhandlers-config' init paramter.
+     */
+    public static final String INIT_PARAM_PROTECTED_HANDLERS_CONFIG = "protectedhandlers-config";
+    
     private static final String PARAM_DIFF = ":diff";
     private static final String PARAM_COPY = ":copy";
     private static final String PARAM_CLONE = ":clone";
     private static final String PARAM_INCLUDE = ":include";
 
     private BatchReadConfig brConfig;
+    private ProtectedRemoveManager protectedRemoveManager;
 
     @Override
     public void init() throws ServletException {
@@ -264,6 +270,13 @@ public abstract class JcrRemotingServlet extends JCRWebdavServerServlet {
             }
         }
 
+        String protectedHandlerConfig = getServletConfig().getInitParameter(INIT_PARAM_PROTECTED_HANDLERS_CONFIG);
+        try {
+            protectedRemoveManager = new ProtectedRemoveManager(protectedHandlerConfig);
+        } catch (IOException e) {
+            log.debug("Unable to create ProtectedRemoveManager from " + protectedHandlerConfig + ".");
+        }
+
         // Determine the configured location for temporary files used when
         // processing file uploads. Since JCR-3029 the default is the
         // standard java.io.tmpdir location, but the presence of explicit
@@ -395,13 +408,13 @@ public abstract class JcrRemotingServlet extends JCRWebdavServerServlet {
                     loc = copy(session, pValues, davResource.getLocator());
                 } else if (data.getParameterValues(PARAM_DIFF) != null) {
                     String targetPath = davResource.getLocator().getRepositoryPath();
-                    processDiff(session, targetPath, data);
+                    processDiff(session, targetPath, data, protectedRemoveManager);
                 } else if ((pValues = data.getParameterValues(PARAM_INCLUDE)) != null
                         && canHandle(DavMethods.DAV_GET, webdavRequest, davResource)) {
                     includes = pValues;
                 } else {
                     String targetPath = davResource.getLocator().getRepositoryPath();
-                    loc = modifyContent(session, targetPath, data);
+                    loc = modifyContent(session, targetPath, data, protectedRemoveManager);
                 }
 
                 // TODO: append entity
@@ -424,7 +437,7 @@ public abstract class JcrRemotingServlet extends JCRWebdavServerServlet {
                     webdavResponse.setStatus(HttpServletResponse.SC_CREATED);
                 }
             } catch (RepositoryException e) {
-                log.warn(e.getMessage());
+                log.warn(e.getMessage(), e);
                 throw new JcrDavException(e);
             } catch (DiffException e) {
                 log.warn(e.getMessage());
@@ -510,11 +523,11 @@ public abstract class JcrRemotingServlet extends JCRWebdavServerServlet {
         return null;
     }
 
-    private static void processDiff(Session session, String targetPath, RequestData data)
+    private static void processDiff(Session session, String targetPath, RequestData data, ProtectedRemoveManager protectedRemoveManager)
             throws RepositoryException, DiffException, IOException {
 
         String[] diffs = data.getParameterValues(PARAM_DIFF);
-        DiffHandler handler = new JsonDiffHandler(session, targetPath, data);
+        DiffHandler handler = new JsonDiffHandler(session, targetPath, data, protectedRemoveManager);
         DiffParser parser = new DiffParser(handler);
 
         for (String diff : diffs) {
@@ -542,10 +555,10 @@ public abstract class JcrRemotingServlet extends JCRWebdavServerServlet {
      * @throws RepositoryException
      * @throws DiffException
      */
-    private static String modifyContent(Session session, String targetPath, RequestData data)
+    private static String modifyContent(Session session, String targetPath, RequestData data, ProtectedRemoveManager protectedRemoveManager)
             throws RepositoryException, DiffException {
 
-        JsonDiffHandler dh = new JsonDiffHandler(session, targetPath, data);
+        JsonDiffHandler dh = new JsonDiffHandler(session, targetPath, data, protectedRemoveManager);
         boolean success = false;
         try {
             for (Iterator<String> pNames = data.getParameterNames(); pNames.hasNext();) {
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonDiffHandler.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonDiffHandler.java
index 3919833..edebe4f 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonDiffHandler.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonDiffHandler.java
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.server.remoting.davex;
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.commons.webdav.JcrValueType;
 import org.apache.jackrabbit.server.util.RequestData;
+import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.commons.json.JsonHandler;
 import org.apache.jackrabbit.commons.json.JsonParser;
 import org.apache.jackrabbit.util.Text;
@@ -33,15 +34,23 @@ import javax.jcr.Item;
 import javax.jcr.ItemNotFoundException;
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
+import javax.jcr.Property;
 import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.Value;
 import javax.jcr.ValueFactory;
+import javax.jcr.ValueFormatException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.ItemDefinition;
+import javax.jcr.nodetype.NodeDefinition;
 import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.NodeTypeManager;
+import javax.jcr.nodetype.PropertyDefinition;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Stack;
 import java.util.LinkedList;
@@ -60,18 +69,27 @@ class JsonDiffHandler implements DiffHandler {
     private final ValueFactory vf;
     private final String requestItemPath;
     private final RequestData data;
+    private final ProtectedRemoveManager protectedRemoveManager;
+
+    private NodeTypeManager ntManager;
 
     JsonDiffHandler(Session session, String requestItemPath, RequestData data) throws RepositoryException {
+        this(session, requestItemPath, data, null);
+    }
+
+    JsonDiffHandler(Session session, String requestItemPath, RequestData data, ProtectedRemoveManager protectedRemoveManager) throws RepositoryException {
         this.session = session;
         this.requestItemPath = requestItemPath;
         this.data = data;
         vf = session.getValueFactory();
+        this.protectedRemoveManager = protectedRemoveManager;
     }
 
     //--------------------------------------------------------< DiffHandler >---
     /**
      * @see DiffHandler#addNode(String, String)
      */
+    @Override
     public void addNode(String targetPath, String diffValue) throws DiffException {
         if (diffValue == null || !(diffValue.startsWith("{") && diffValue.endsWith("}"))) {
             throw new DiffException("Invalid 'addNode' value '" + diffValue + "'");
@@ -92,6 +110,7 @@ class JsonDiffHandler implements DiffHandler {
     /**
      * @see DiffHandler#setProperty(String, String) 
      */
+    @Override
     public void setProperty(String targetPath, String diffValue) throws DiffException {
         try {
             String itemPath = getItemPath(targetPath);
@@ -156,13 +175,24 @@ class JsonDiffHandler implements DiffHandler {
     /**
      * @see DiffHandler#remove(String, String) 
      */
+    @Override
     public void remove(String targetPath, String diffValue) throws DiffException {
         if (!(diffValue == null || diffValue.trim().length() == 0)) {
             throw new DiffException("'remove' may not have a diffValue.");
         }
         try {
             String itemPath = getItemPath(targetPath);
-            session.getItem(itemPath).remove();
+            Item item = session.getItem(itemPath);
+            
+            ItemDefinition def = (item.isNode()) ? ((Node) item).getDefinition() : ((Property) item).getDefinition();
+            if (def.isProtected()) {
+                // delegate to the manager.
+                if (protectedRemoveManager == null || !protectedRemoveManager.remove(session, itemPath)) {
+                   throw new ConstraintViolationException("Cannot remove protected node: no suitable handler configured.");
+                }
+            } else {
+                item.remove();
+            }
         } catch (RepositoryException e) {
             throw new DiffException(e.getMessage(), e);
         }
@@ -171,6 +201,7 @@ class JsonDiffHandler implements DiffHandler {
     /**
      * @see DiffHandler#move(String, String) 
      */
+    @Override
     public void move(String targetPath, String diffValue) throws DiffException {
         if (diffValue == null || diffValue.length() == 0) {
             throw new DiffException("Invalid 'move' value '" + diffValue + "'");
@@ -276,6 +307,13 @@ class JsonDiffHandler implements DiffHandler {
         }
     }
 
+    private NodeTypeManager getNodeTypeManager() throws RepositoryException {
+        if (ntManager == null) {
+            ntManager = session.getWorkspace().getNodeTypeManager();
+        }
+        return ntManager;
+    }
+
     private static String normalize(String path) {
         if (path.indexOf('.') == -1) {
             return path;
@@ -298,14 +336,18 @@ class JsonDiffHandler implements DiffHandler {
         }
         return "/" + Text.implode(queue.toArray(new String[queue.size()]), "/");
     }
-    
+
+    private static ContentHandler createContentHandler(Node parent) throws RepositoryException {
+        return parent.getSession().getImportContentHandler(parent.getPath(), ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
+    }
+
     private static Node importNode(Node parent, String nodeName, String ntName,
                                    String uuid) throws RepositoryException {
 
         String uri = "http://www.jcp.org/jcr/sv/1.0";
         String prefix = "sv:";
 
-        ContentHandler ch = parent.getSession().getImportContentHandler(parent.getPath(), ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
+        ContentHandler ch = createContentHandler(parent);
         try {
             ch.startDocument();
 
@@ -460,31 +502,40 @@ class JsonDiffHandler implements DiffHandler {
     private final class ValueHandler implements JsonHandler {
         private Value v;
 
+        @Override
         public void object() throws IOException {
             // ignore
         }
+        @Override
         public void endObject() throws IOException {
             // ignore
         }
+        @Override
         public void array() throws IOException {
             // ignore
         }
+        @Override
         public void endArray() throws IOException {
             // ignore
         }
+        @Override
         public void key(String key) throws IOException {
             // ignore
         }
 
+        @Override
         public void value(String value) throws IOException {
             v = (value == null) ? null : vf.createValue(value);
         }
+        @Override
         public void value(boolean value) throws IOException {
             v = vf.createValue(value);
         }
+        @Override
         public void value(long value) throws IOException {
             v = vf.createValue(value);
         }
+        @Override
         public void value(double value) throws IOException {
             v = vf.createValue(value);
         }
@@ -500,22 +551,28 @@ class JsonDiffHandler implements DiffHandler {
     private final class ValuesHandler implements JsonHandler {
         private List<Value> values = new ArrayList<Value>();
 
+        @Override
         public void object() throws IOException {
             // ignore
         }
+        @Override
         public void endObject() throws IOException {
             // ignore
         }
+        @Override
         public void array() throws IOException {
             // ignore
         }
+        @Override
         public void endArray() throws IOException {
             // ignore
         }
+        @Override
         public void key(String key) throws IOException {
             // ignore
         }
 
+        @Override
         public void value(String value) throws IOException {
             if (value != null) {
                 values.add(vf.createValue(value));
@@ -523,12 +580,15 @@ class JsonDiffHandler implements DiffHandler {
                 log.warn("Null element for a multivalued property -> Ignore.");
             }
         }
+        @Override
         public void value(boolean value) throws IOException {
             values.add(vf.createValue(value));
         }
+        @Override
         public void value(long value) throws IOException {
             values.add(vf.createValue(value));
         }
+        @Override
         public void value(double value) throws IOException {
             values.add(vf.createValue(value));
         }
@@ -553,19 +613,29 @@ class JsonDiffHandler implements DiffHandler {
             key = nodeName;
         }
 
+        @Override
         public void object() throws IOException {
-            ImportNode n = new ImportNode(key);
-            if (!st.isEmpty()) {
-                ImportItem obj = st.peek();
-                if (obj instanceof ImportNode) {
-                    ((ImportNode) obj).addNode(n);
-                } else {
-                    throw new DiffException("Invalid DIFF format: The JSONArray may only contain simple values.");
-                }
-            }
+        	ImportNode n;
+        	if (st.isEmpty()) {
+        		try {
+            		n = new ImportNode(parent.getPath(), key);        			
+        		} catch (RepositoryException e) {
+        			throw new DiffException(e.getMessage(), e);
+        		}
+
+        	} else {
+        		ImportItem obj = st.peek();
+                n = new ImportNode(obj.getPath(), key);                                                    
+                if (obj instanceof ImportNode) {                        
+                	((ImportNode) obj).addNode(n);                    
+                } else {                
+                	throw new DiffException("Invalid DIFF format: The JSONArray may only contain simple values.");                    
+                }        		
+        	}
             st.push(n);
         }
 
+        @Override
         public void endObject() throws IOException {
             // element on stack must be ImportMvProp since array may only
             // contain simple values, no arrays/objects are allowed.
@@ -576,7 +646,14 @@ class JsonDiffHandler implements DiffHandler {
             if (st.isEmpty()) {
                 // everything parsed -> start adding all nodes and properties
                 try {
-                    obj.createItem(parent);                    
+                    if (obj.mandatesImport(parent)) {
+                        obj.importItem(createContentHandler(parent));
+                    } else {
+                        obj.createItem(parent);
+                    }
+                } catch (IOException e) {
+                    log.error(e.getMessage());
+                    throw new DiffException(e.getMessage(), e);
                 } catch (RepositoryException e) {
                     log.error(e.getMessage());
                     throw new DiffException(e.getMessage(), e);
@@ -584,9 +661,10 @@ class JsonDiffHandler implements DiffHandler {
             }
         }
 
-        public void array() throws IOException {
-            ImportMvProp prop = new ImportMvProp(key);
+        @Override
+        public void array() throws IOException {            
             ImportItem obj = st.peek();
+            ImportMvProp prop = new ImportMvProp(obj.getPath(), key);
             if (obj instanceof ImportNode) {
                 ((ImportNode)obj).addProp(prop);
             } else {
@@ -595,6 +673,7 @@ class JsonDiffHandler implements DiffHandler {
             st.push(prop);
         }
 
+        @Override
         public void endArray() throws IOException {
             // element on stack must be ImportMvProp since array may only
             // contain simple values, no arrays/objects are allowed.
@@ -604,24 +683,29 @@ class JsonDiffHandler implements DiffHandler {
             }
         }
 
+        @Override
         public void key(String key) throws IOException {
             this.key = key;
         }
 
+        @Override
         public void value(String value) throws IOException {
             Value v = (value == null) ? null : vf.createValue(value);
             value(v);
         }
 
+        @Override
         public void value(boolean value) throws IOException {
             value(vf.createValue(value));
         }
 
+        @Override
         public void value(long value) throws IOException {
             Value v = vf.createValue(value);
             value(v);
         }
 
+        @Override
         public void value(double value) throws IOException {
             value(vf.createValue(value));
         }
@@ -631,49 +715,109 @@ class JsonDiffHandler implements DiffHandler {
             if (obj instanceof ImportMvProp) {
                 ((ImportMvProp) obj).values.add(v);
             } else {
-                ((ImportNode) obj).addProp(new ImportProp(key, v));
+                ((ImportNode) obj).addProp(new ImportProp(obj.getPath(), key, v));
             }
         }
     }
 
     private abstract class ImportItem {
+
+        static final String TYPE_CDATA = "CDATA";
+
+        final String parentPath;
         final String name;
-        private ImportItem(String name) throws IOException {
+        final String path;
+        
+        private ImportItem(String parentPath, String name) throws IOException {
             if (name == null) {
                 throw new DiffException("Invalid DIFF format: NULL key.");
             }
             this.name = name;
+            this.parentPath = parentPath;
+            this.path = parentPath+"/"+name;
+        }
+        
+        void setNameAttribute(AttributesImpl attr) {
+            attr.addAttribute(Name.NS_SV_URI, "name", Name.NS_SV_PREFIX +":name", TYPE_CDATA, name);
+        }
+
+        String getPath() {
+        	return path;
         }
+        
+        abstract boolean mandatesImport(Node parent);
 
-        abstract void createItem(Node parent) throws RepositoryException;
+        abstract void createItem(Node parent) throws RepositoryException, IOException;
+
+        abstract void importItem(ContentHandler contentHandler) throws IOException;
     }
     
     private final class ImportNode extends ImportItem {
-        private String ntName;
-        private String uuid;
+
+        private static final String LOCAL_NAME = "node";
+
+        private ImportProp ntName;
+        private ImportProp uuid;
 
         private List<ImportNode> childN = new ArrayList<ImportNode>();
-        private List<ImportItem> childP = new ArrayList<ImportItem>();
+        private List<ImportProperty> childP = new ArrayList<ImportProperty>();
 
-        private ImportNode(String name) throws IOException {
-            super(name);
+        private ImportNode(String parentPath, String name) throws IOException {
+            super(parentPath, name);
         }
 
-        void addProp(ImportProp prop) {
-            if (prop.name.equals(JcrConstants.JCR_PRIMARYTYPE)) {
+        private String getUUID() {
+            if (uuid != null && uuid.value != null) {
                 try {
-                    ntName = (prop.value == null) ? null : prop.value.getString();
+                    return uuid.value.getString();
                 } catch (RepositoryException e) {
-                    // should never get here. Value.getString() should always succeed.
                     log.error(e.getMessage());
                 }
-            } else if (prop.name.equals(JcrConstants.JCR_UUID)) {
+            }
+            return null;
+        }
+
+        private String getPrimaryType() {
+            if (ntName != null && ntName.value != null) {
                 try {
-                    uuid = (prop.value == null) ? null : prop.value.getString();
+                    return ntName.value.getString();
                 } catch (RepositoryException e) {
-                    // should never get here. Value.getString() should always succeed.
                     log.error(e.getMessage());
                 }
+            }
+            return null;
+        }
+
+        @Override
+        boolean mandatesImport(Node parent) {
+            String primaryType = getPrimaryType();
+            // Very simplistic and simplified test for protection that doesn't
+            // take mixin types into account and ignores all JCR primary types
+            if (!primaryType.startsWith(Name.NS_NT_PREFIX)) {
+                try {
+                    NodeType nt = getNodeTypeManager().getNodeType(primaryType);
+                    for (NodeDefinition nd : nt.getChildNodeDefinitions()) {
+                        if (nd.isProtected()) {
+                            return true;
+                        }
+                    }
+                    for (PropertyDefinition pd : nt.getPropertyDefinitions()) {
+                        if (!pd.getName().startsWith(Name.NS_JCR_PREFIX) && pd.isProtected()) {
+                            return true;
+                        }
+                    }
+                } catch (RepositoryException e) {
+                    log.warn(e.getMessage(), e);
+                }
+            }
+            return false;
+        }
+
+        void addProp(ImportProp prop) {
+            if (prop.name.equals(JcrConstants.JCR_PRIMARYTYPE)) {
+                ntName = prop;
+            } else if (prop.name.equals(JcrConstants.JCR_UUID)) {
+                uuid = prop;
             } else {
                 // regular property
                 childP.add(prop);
@@ -686,46 +830,159 @@ class JsonDiffHandler implements DiffHandler {
 
         void addNode(ImportNode node) {
             childN.add(node);
+        }        
+        
+        @Override
+        void importItem(ContentHandler contentHandler) throws IOException {
+            try {
+                AttributesImpl attr = new AttributesImpl();
+                setNameAttribute(attr);
+                contentHandler.startElement(Name.NS_SV_URI, LOCAL_NAME, Name.NS_SV_PREFIX+":"+LOCAL_NAME, attr);
+
+                if (ntName != null && ntName.value != null) {
+                    ntName.importItem(contentHandler);
+                }
+                if (uuid != null && uuid.value != null) {
+                    uuid.importItem(contentHandler);
+                }
+
+                for(ImportProperty prop : childP) {
+                    prop.importItem(contentHandler);
+                }
+
+                for(ImportNode node : childN) {
+                    node.importItem(contentHandler);
+                }
+                contentHandler.endElement(Name.NS_SV_URI, LOCAL_NAME, Name.NS_SV_PREFIX+":"+LOCAL_NAME);
+            } catch(SAXException e) {
+                throw new DiffException(e.getMessage(), e);
+            }
         }
 
         @Override
-        void createItem(Node parent) throws RepositoryException {
-            Node n;
-            if (uuid == null) {
-                n = (ntName == null) ? parent.addNode(name) : parent.addNode(name,  ntName);
+        void createItem(Node parent) throws RepositoryException, IOException {
+            if (mandatesImport(parent)) {
+                ContentHandler ch = createContentHandler(parent);
+                try {
+                    ch.startDocument();
+                    importItem(ch);
+                    ch.endDocument();
+                } catch (SAXException e) {
+                    throw new DiffException(e.getMessage(), e);
+                }
             } else {
-                n = importNode(parent, name, ntName, uuid);
+                Node n;
+                String uuidValue = getUUID();
+                String primaryType = getPrimaryType();
+                if (uuidValue == null) {
+                    n = (primaryType == null) ? parent.addNode(name) : parent.addNode(name,  primaryType);
+                } else {
+                    n = importNode(parent, name, primaryType, uuidValue);
+                }
+                // create all properties
+                for (ImportItem obj : childP) {
+                    obj.createItem(n);
+                }
+                // recursively create all child nodes
+                for (ImportItem obj : childN) {
+                    obj.createItem(n);
+                }
             }
-            // create all properties
-            for (ImportItem obj : childP) {
-                obj.createItem(n);
+        }
+    }
+
+    private abstract class ImportProperty extends ImportItem {
+
+        static final String VALUE = "value";
+        static final String TYPE = "type";
+        static final String LOCAL_NAME = "property";
+
+        private ImportProperty(String parentPath, String name) throws IOException {
+            super(parentPath, name);
+        }
+
+        @Override
+        boolean mandatesImport(Node parent) {
+            // TODO: verify again if a protected property (except for jcr:primaryType and jcr:mixinTypes) will ever change outside the scope of importing the whole tree.
+            return false;
+        }
+
+        @Override
+        void importItem(ContentHandler contentHandler) throws IOException {
+            try {
+                AttributesImpl propAtts = new AttributesImpl();
+                setNameAttribute(propAtts);
+                setTypeAttribute(propAtts);
+                contentHandler.startElement(Name.NS_SV_URI, LOCAL_NAME, Name.NS_SV_PREFIX+":"+LOCAL_NAME, propAtts);
+                startValueElement(contentHandler);
+                contentHandler.endElement(Name.NS_SV_URI, LOCAL_NAME, Name.NS_SV_PREFIX+":"+LOCAL_NAME);
+            } catch(SAXException e) {
+                throw new DiffException(e.getMessage(), e);
             }
-            // recursively create all child nodes
-            for (ImportItem obj : childN) {
-                obj.createItem(n);
+        }
+
+        void setTypeAttribute(AttributesImpl attr) {
+            String type = null;
+            if (name.equals(JcrConstants.JCR_PRIMARYTYPE)) {
+                type = PropertyType.nameFromValue(PropertyType.NAME);
+            } else if (name.equals(JcrConstants.JCR_MIXINTYPES)) {
+                type = PropertyType.nameFromValue(PropertyType.NAME);
+            } else if (name.equals(JcrConstants.JCR_UUID)) {
+                type = PropertyType.nameFromValue(PropertyType.STRING);
+            } else {
+                type = PropertyType.nameFromValue(PropertyType.UNDEFINED);
             }
+            attr.addAttribute(Name.NS_SV_URI, TYPE, Name.NS_SV_PREFIX+":"+TYPE, TYPE_CDATA, type);
         }
+
+        abstract void startValueElement(ContentHandler contentHandler) throws IOException;
     }
 
-    private final class ImportProp extends ImportItem  {
+    private final class ImportProp extends ImportProperty {
+
         private final Value value;
 
-        private ImportProp(String name, Value v) throws IOException {
-            super(name);
-            this.value = v;
+        private ImportProp(String parentPath, String name, Value value) throws IOException {
+            super(parentPath, name);
+            try {
+                if (value == null) {
+            		this.value = extractValuesFromRequest(getPath())[0];
+            	} else {
+            		this.value = value;
+            	}                        	
+            } catch (RepositoryException e) {
+            	throw new DiffException(e.getMessage(), e);
+            }
         }
 
         @Override
         void createItem(Node parent) throws RepositoryException {
             parent.setProperty(name, value);
         }
+
+        @Override
+        void startValueElement(ContentHandler contentHandler) throws IOException {
+            try {            	
+                String str = value.getString();
+                contentHandler.startElement(Name.NS_SV_URI, VALUE, Name.NS_SV_PREFIX+":"+VALUE, new AttributesImpl());
+                contentHandler.characters(str.toCharArray(), 0, str.length());
+                contentHandler.endElement(Name.NS_SV_URI, VALUE, Name.NS_SV_PREFIX+":"+VALUE);
+            } catch(SAXException e) {
+                throw new DiffException(e.getMessage());
+            } catch (ValueFormatException e) {
+                throw new DiffException(e.getMessage());
+            } catch (RepositoryException e) {
+                throw new DiffException(e.getMessage());
+            }
+        }
     }
 
-    private final class ImportMvProp extends ImportItem  {
+    private final class ImportMvProp extends ImportProperty  {
+
         private List<Value> values = new ArrayList<Value>();
 
-        private ImportMvProp(String name) throws IOException {
-            super(name);
+        private ImportMvProp(String parentPath, String name) throws IOException {
+            super(parentPath, name);
         }
 
         @Override
@@ -734,7 +991,30 @@ class JsonDiffHandler implements DiffHandler {
             if (JcrConstants.JCR_MIXINTYPES.equals(name)) {
                 setMixins(parent, vls);
             } else {
-                parent.setProperty(name, vls);            
+                parent.setProperty(name, vls);
+            }
+        }
+
+        @Override
+        void startValueElement(ContentHandler contentHandler) throws IOException {
+            try {
+            	// Multi-valued property with values present in the request multi-part             	
+            	if (values.size() == 0) {            	
+            		values = Arrays.asList(extractValuesFromRequest(getPath()));            	   
+            	}
+            	
+                for (Value v : values) {
+                    String str = v.getString();
+                    contentHandler.startElement(Name.NS_SV_URI, VALUE, Name.NS_SV_PREFIX+":"+VALUE, new AttributesImpl());
+                    contentHandler.characters(str.toCharArray(), 0, str.length());
+                    contentHandler.endElement(Name.NS_SV_URI, VALUE, Name.NS_SV_PREFIX+":"+VALUE);
+                }
+            } catch(SAXException e) {
+                throw new DiffException(e.getMessage());
+            } catch (ValueFormatException e) {
+                throw new DiffException(e.getMessage());
+            } catch (RepositoryException e) {
+                throw new DiffException(e.getMessage());
             }
         }
     }
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonWriter.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonWriter.java
index d4e5058..3a55846 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonWriter.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/JsonWriter.java
@@ -16,6 +16,9 @@
  */
 package org.apache.jackrabbit.server.remoting.davex;
 
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collection;
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
 import javax.jcr.Property;
@@ -23,9 +26,6 @@ import javax.jcr.PropertyIterator;
 import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 import javax.jcr.Value;
-import java.io.Writer;
-import java.io.IOException;
-import java.util.Collection;
 
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.commons.json.JsonUtil;
@@ -33,7 +33,7 @@ import org.apache.jackrabbit.commons.json.JsonUtil;
 /**
  * <code>JsonWriter</code> traverses a tree of JCR items and writes a JSON object
  * exposing nodes as JSON object members and properties as JSON pairs.
- * <p/>
+ * <p>
  * <strong>Note</strong>: Using JSON.org library is deliberately avoided for the
  * following reasons.
  * <ul>
@@ -188,11 +188,11 @@ class JsonWriter {
             }
         } else {
             boolean isMultiple = p.isMultiple();
-            if (requiresTypeInfo(type) || (isMultiple && p.getValues().length == 0)) {
+            if (requiresTypeInfo(p) || (isMultiple && p.getValues().length == 0)) {
                 /* special property types that have no correspondence in JSON
                    are transported as String. the type is transported with an
                    extra key-value pair, the key having a leading ':' the value
-                   reflects the type. 
+                   reflects the type.
                    the same applies for multivalued properties consisting of an
                    empty array -> property type guessing would not be possible.
                  */
@@ -208,8 +208,8 @@ class JsonWriter {
         }
     }
 
-    private static boolean requiresTypeInfo(int type) {
-        switch (type) {
+    private static boolean requiresTypeInfo(Property p) throws RepositoryException {
+        switch (p.getType()) {
             case PropertyType.NAME:
             case PropertyType.PATH:
             case PropertyType.REFERENCE:
@@ -218,6 +218,22 @@ class JsonWriter {
             case PropertyType.URI:
             case PropertyType.DECIMAL:
                 return true;
+            case PropertyType.DOUBLE:
+                /* Double.NaN, Double.POSITIVE_INFINITY and Double.NEGATIVE_INFINITY
+                   are not defined in JSON, so they have to be serialized as type-hinted strings.
+                 */
+                if (p.isMultiple()) {
+                    for (Value val : p.getValues()) {
+                        double doubleValue = val.getDouble();
+                        if (Double.isInfinite(doubleValue) || Double.isNaN(doubleValue)) {
+                            return true;
+                        }
+                    }
+                    return false;
+                } else {
+                    double doubleValue = p.getDouble();
+                    return Double.isInfinite(doubleValue) || Double.isNaN(doubleValue);
+                }
             default:
                 // any other property type
                 return false;
@@ -290,9 +306,16 @@ class JsonWriter {
 
             case PropertyType.BOOLEAN:
             case PropertyType.LONG:
-            case PropertyType.DOUBLE:
                 writer.write(v.getString());
                 break;
+            case PropertyType.DOUBLE:
+                double d = v.getDouble();
+                String str = v.getString();
+                if (Double.isNaN(d) || Double.isInfinite(d)) {
+                    str = JsonUtil.getJsonString(str);
+                }
+                writer.write(str);
+                break;
 
             default:
                 writer.write(JsonUtil.getJsonString(v.getString()));
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/ProtectedItemRemoveHandler.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/ProtectedItemRemoveHandler.java
new file mode 100644
index 0000000..4b8ccf2
--- /dev/null
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/ProtectedItemRemoveHandler.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.server.remoting.davex;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+/**
+ * ProtectedItemRemoveHandler... TODO
+ */
+public interface ProtectedItemRemoveHandler {
+
+    public boolean remove(Session session, String itemPath) throws RepositoryException;
+}
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/ProtectedRemoveManager.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/ProtectedRemoveManager.java
new file mode 100644
index 0000000..92052ce
--- /dev/null
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/remoting/davex/ProtectedRemoveManager.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.server.remoting.davex;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Properties;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ProtectedRemoveManager {
+
+    private static final Logger log = LoggerFactory.getLogger(ProtectedRemoveManager.class);
+
+    private List<ProtectedItemRemoveHandler> handlers = new ArrayList<ProtectedItemRemoveHandler>();
+
+    public ProtectedRemoveManager(String config) throws IOException {
+
+    	 if (config == null) {
+             log.warn("protectedhandlers-config is missing -> DIFF processing can fail for the Remove operation if the content to" +
+                     "remove is protected!");
+         } else {
+        	 File file = new File(config);
+        	 if (file.exists()) {         
+        		 InputStream inStream = new FileInputStream(file);
+                 Properties props = new Properties();
+        		 props.load(inStream);             
+        		 for (Enumeration<?> en = props.propertyNames(); en.hasMoreElements();) {             
+        			 String key = en.nextElement().toString();                 
+        			 String className = props.getProperty(key);                 
+        			 if (!className.isEmpty()) {                 
+        				 ProtectedItemRemoveHandler irHandler = createHandler(className);                     
+        				 if (irHandler != null) {                     
+        					 handlers.add(irHandler);                        
+        				 }                    
+        			 }                
+        		 }            
+        	 } else { // config is an Impl class
+        		 if (!config.isEmpty()) {        	    	
+        			 ProtectedItemRemoveHandler irHandler = createHandler(config);        	         
+        			 if (irHandler != null) {        	         
+        				 handlers.add(irHandler);        	            
+        			 }        	    	
+        		 } else {        	    
+        			 log.debug("Fail to locate the protected-item-remove-handler properties file.");        	    	
+        		 }
+        	 }        
+         }    	 
+    }
+
+    public boolean remove(Session session, String itemPath) throws RepositoryException {
+        for (ProtectedItemRemoveHandler handler : handlers) {
+            if (handler.remove(session, itemPath)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Instantiates and returns a concrete ProtectedItemRemoveHandler implementation.
+     * @param className
+     * @return
+     * @throws RepositoryException
+     */
+    private static ProtectedItemRemoveHandler createHandler(String className) {
+        try {
+            Class<?> irHandlerClass = Class.forName(className);
+            if (ProtectedItemRemoveHandler.class.isAssignableFrom(irHandlerClass)) {
+                ProtectedItemRemoveHandler irHandler = (ProtectedItemRemoveHandler) irHandlerClass.newInstance();
+                return irHandler;
+            }
+        } catch (ClassNotFoundException e) {
+            log.error(e.getMessage(), e);
+        } catch (InstantiationException e) {
+            log.error(e.getMessage(), e);
+        } catch (IllegalAccessException e) {
+            log.error(e.getMessage(), e);
+        }
+        return null;
+    }
+}
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/util/HttpMultipartPost.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/util/HttpMultipartPost.java
index f59aae6..ba8b15e 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/util/HttpMultipartPost.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/util/HttpMultipartPost.java
@@ -204,7 +204,7 @@ class HttpMultipartPost {
      * Returns the first value of the file items with the given <code>name</code>.
      * The byte to string converstion is done using either the contenttype of
      * the file items or the <code>formEncoding</code>.
-     * <p/>
+     * <p>
      * Please note that if the addressed parameter is an uploaded file rather
      * than a simple form entry, the name of the original file is returned    
      * instead of the content.
@@ -232,7 +232,7 @@ class HttpMultipartPost {
      * Returns an array of Strings with all values of the parameter addressed
      * by <code>name</code>. the byte to string conversion is done using either
      * the content type of the multipart body or the <code>formEncoding</code>.
-     * <p/>
+     * <p>
      * Please note that if the addressed parameter is an uploaded file rather
      * than a simple form entry, the name of the original file is returned
      * instead of the content.
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/util/RequestData.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/util/RequestData.java
index e02d20a..aa4f960 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/util/RequestData.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/server/util/RequestData.java
@@ -60,7 +60,7 @@ public class RequestData {
      * Returns the first value of the parameter with the given <code>name</code>.
      * The byte to string conversion is done using either the content type of
      * the parameter or the <code>formEncoding</code>.
-     * <p/>
+     * <p>
      * Please note that if the addressed parameter is a file parameter, the
      * name of the original file is returned, and not its content.
      *
@@ -91,7 +91,7 @@ public class RequestData {
      * Returns an array of Strings with all values of the parameter addressed
      * by <code>name</code>. the byte to string conversion is done using either
      * the content type of the multipart body or the <code>formEncoding</code>.
-     * <p/>
+     * <p>
      * Please note that if the addressed parameter is a file parameter, the
      * name of the original file is returned, and not its content.
      *
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/AbstractItemResource.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/AbstractItemResource.java
index 8a68c16..cf60e83 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/AbstractItemResource.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/AbstractItemResource.java
@@ -17,30 +17,27 @@
 package org.apache.jackrabbit.webdav.jcr;
 
 import org.apache.jackrabbit.util.Text;
+import org.apache.jackrabbit.webdav.DavCompliance;
 import org.apache.jackrabbit.webdav.DavException;
 import org.apache.jackrabbit.webdav.DavResource;
 import org.apache.jackrabbit.webdav.DavResourceFactory;
 import org.apache.jackrabbit.webdav.DavResourceLocator;
 import org.apache.jackrabbit.webdav.DavServletResponse;
-import org.apache.jackrabbit.webdav.DavCompliance;
 import org.apache.jackrabbit.webdav.io.OutputContext;
+import org.apache.jackrabbit.webdav.jcr.nodetype.ItemDefinitionImpl;
+import org.apache.jackrabbit.webdav.jcr.nodetype.NodeDefinitionImpl;
+import org.apache.jackrabbit.webdav.jcr.nodetype.PropertyDefinitionImpl;
 import org.apache.jackrabbit.webdav.jcr.property.JcrDavPropertyNameSet;
+import org.apache.jackrabbit.webdav.observation.EventDiscovery;
 import org.apache.jackrabbit.webdav.observation.ObservationConstants;
 import org.apache.jackrabbit.webdav.observation.ObservationResource;
-import org.apache.jackrabbit.webdav.observation.SubscriptionManager;
 import org.apache.jackrabbit.webdav.observation.Subscription;
 import org.apache.jackrabbit.webdav.observation.SubscriptionInfo;
-import org.apache.jackrabbit.webdav.observation.EventDiscovery;
-import org.apache.jackrabbit.webdav.jcr.nodetype.ItemDefinitionImpl;
-import org.apache.jackrabbit.webdav.jcr.nodetype.NodeDefinitionImpl;
-import org.apache.jackrabbit.webdav.jcr.nodetype.PropertyDefinitionImpl;
-import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
-import org.apache.jackrabbit.webdav.property.HrefProperty;
+import org.apache.jackrabbit.webdav.observation.SubscriptionManager;
 import org.apache.jackrabbit.webdav.property.DavProperty;
 import org.apache.jackrabbit.webdav.property.DavPropertyName;
-import org.apache.jackrabbit.webdav.security.CurrentUserPrivilegeSetProperty;
-import org.apache.jackrabbit.webdav.security.Privilege;
-import org.apache.jackrabbit.webdav.security.SecurityConstants;
+import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
+import org.apache.jackrabbit.webdav.property.HrefProperty;
 import org.apache.jackrabbit.webdav.transaction.TxLockEntry;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -51,9 +48,6 @@ import javax.jcr.PathNotFoundException;
 import javax.jcr.Property;
 import javax.jcr.RepositoryException;
 import javax.jcr.Workspace;
-import java.security.AccessControlException;
-import java.util.ArrayList;
-import java.util.List;
 import java.io.IOException;
 
 /**
@@ -134,27 +128,6 @@ abstract class AbstractItemResource extends AbstractResource implements
             } else if (ObservationConstants.SUBSCRIPTIONDISCOVERY.equals(name)) {
                 // observation resource
                 prop = subsMgr.getSubscriptionDiscovery(this);
-            } else if (SecurityConstants.CURRENT_USER_PRIVILEGE_SET.equals(name)) {
-                // TODO complete set of properties defined by RFC 3744
-                Privilege[] allPrivs = new Privilege[] {PRIVILEGE_JCR_READ,
-                        PRIVILEGE_JCR_ADD_NODE,
-                        PRIVILEGE_JCR_SET_PROPERTY,
-                        PRIVILEGE_JCR_REMOVE};
-                List<Privilege> currentPrivs = new ArrayList<Privilege>();
-                for (Privilege priv : allPrivs) {
-                    try {
-                        String path = getLocator().getRepositoryPath();
-                        getRepositorySession().checkPermission(path, priv.getName());
-                        currentPrivs.add(priv);
-                    } catch (AccessControlException e) {
-                        // ignore
-                        log.debug(e.toString());
-                    } catch (RepositoryException e) {
-                        // ignore
-                        log.debug(e.toString());
-                    }
-                }
-                prop =  new CurrentUserPrivilegeSetProperty(currentPrivs.toArray(new Privilege[currentPrivs.size()]));
             }
         }
 
@@ -164,6 +137,7 @@ abstract class AbstractItemResource extends AbstractResource implements
     /**
      * @see org.apache.jackrabbit.webdav.DavResource#getSupportedMethods()
      */
+    @Override
     public String getSupportedMethods() {
         return ItemResourceConstants.METHODS;
     }
@@ -174,6 +148,7 @@ abstract class AbstractItemResource extends AbstractResource implements
      *
      * @see org.apache.jackrabbit.webdav.DavResource#exists()
      */
+    @Override
     public boolean exists() {
         return item != null;
     }
@@ -188,6 +163,7 @@ abstract class AbstractItemResource extends AbstractResource implements
      *
      * @see org.apache.jackrabbit.webdav.DavResource#getDisplayName()
      */
+    @Override
     public String getDisplayName() {
         String resPath = getResourcePath();
         return (resPath != null) ? Text.getName(resPath) : resPath;
@@ -200,6 +176,7 @@ abstract class AbstractItemResource extends AbstractResource implements
      *
      * @see DavResource#spool(OutputContext)
      */
+    @Override
     public void spool(OutputContext outputContext) throws IOException {
         if (!initedProps) {
             initProperties();
@@ -241,6 +218,7 @@ abstract class AbstractItemResource extends AbstractResource implements
      * repository node.
      * @see org.apache.jackrabbit.webdav.DavResource#getCollection()
      */
+    @Override
     public DavResource getCollection() {
         DavResource collection = null;
 
@@ -297,7 +275,7 @@ abstract class AbstractItemResource extends AbstractResource implements
      * the locator of the specified destination resource indicates a different
      * workspace, {@link Workspace#copy(String, String, String)} is used to perform
      * the copy operation, {@link Workspace#copy(String, String)} otherwise.
-     * <p/>
+     * <p>
      * Note, that this implementation does not support shallow copy.
      *
      * @param destination
@@ -339,6 +317,7 @@ abstract class AbstractItemResource extends AbstractResource implements
     /**
      * @see ObservationResource#init(SubscriptionManager)
      */
+    @Override
     public void init(SubscriptionManager subsMgr) {
         this.subsMgr = subsMgr;
     }
@@ -347,6 +326,7 @@ abstract class AbstractItemResource extends AbstractResource implements
      * @see ObservationResource#subscribe(org.apache.jackrabbit.webdav.observation.SubscriptionInfo, String)
      * @see SubscriptionManager#subscribe(org.apache.jackrabbit.webdav.observation.SubscriptionInfo, String, org.apache.jackrabbit.webdav.observation.ObservationResource)
      */
+    @Override
     public Subscription subscribe(SubscriptionInfo info, String subscriptionId)
             throws DavException {
         return subsMgr.subscribe(info, subscriptionId, this);
@@ -356,6 +336,7 @@ abstract class AbstractItemResource extends AbstractResource implements
      * @see ObservationResource#unsubscribe(String)
      * @see SubscriptionManager#unsubscribe(String, org.apache.jackrabbit.webdav.observation.ObservationResource)
      */
+    @Override
     public void unsubscribe(String subscriptionId) throws DavException {
         subsMgr.unsubscribe(subscriptionId, this);
     }
@@ -364,6 +345,7 @@ abstract class AbstractItemResource extends AbstractResource implements
      * @see ObservationResource#poll(String, long)
      * @see SubscriptionManager#poll(String, long, org.apache.jackrabbit.webdav.observation.ObservationResource)
      */
+    @Override
     public EventDiscovery poll(String subscriptionId, long timeout) throws DavException {
         return subsMgr.poll(subscriptionId, timeout, this);
     }
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/AbstractResource.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/AbstractResource.java
index ad0d643..b6f1a48 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/AbstractResource.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/AbstractResource.java
@@ -66,8 +66,10 @@ import org.apache.jackrabbit.webdav.version.report.Report;
 import org.apache.jackrabbit.webdav.version.report.ReportInfo;
 import org.apache.jackrabbit.webdav.version.report.ReportType;
 import org.apache.jackrabbit.webdav.version.report.SupportedReportSetProperty;
+import org.apache.jackrabbit.webdav.xml.DomUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.w3c.dom.Element;
 
 import javax.jcr.Item;
 import javax.jcr.RepositoryException;
@@ -75,6 +77,8 @@ import javax.jcr.Session;
 import javax.jcr.observation.EventListener;
 import javax.jcr.observation.Event;
 import javax.jcr.observation.EventIterator;
+import javax.xml.parsers.ParserConfigurationException;
+
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.Iterator;
@@ -146,6 +150,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      * @return string listing the compliance classes.
      * @see org.apache.jackrabbit.webdav.DavResource#getComplianceClass()
      */
+    @Override
     public String getComplianceClass() {
         return COMPLIANCE_CLASSES;
     }
@@ -153,6 +158,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
     /**
      * @see org.apache.jackrabbit.webdav.DavResource#getLocator()
      */
+    @Override
     public DavResourceLocator getLocator() {
         return locator;
     }
@@ -166,6 +172,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      * @see DavResource#getResourcePath()
      * @see org.apache.jackrabbit.webdav.DavResourceLocator#getResourcePath()
      */
+    @Override
     public String getResourcePath() {
         return locator.getResourcePath();
     }
@@ -174,6 +181,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      * @see DavResource#getHref()
      * @see DavResourceLocator#getHref(boolean)
      */
+    @Override
     public String getHref() {
         return locator.getHref(isCollection());
     }
@@ -181,6 +189,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
     /**
      * @see org.apache.jackrabbit.webdav.DavResource#getPropertyNames()
      */
+    @Override
     public DavPropertyName[] getPropertyNames() {
         initPropertyNames();
         return names.getContent().toArray(new DavPropertyName[names.getContentSize()]);
@@ -189,6 +198,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
     /**
      * @see org.apache.jackrabbit.webdav.DavResource#getProperty(org.apache.jackrabbit.webdav.property.DavPropertyName)
      */
+    @Override
     public DavProperty<?> getProperty(DavPropertyName name) {
         DavProperty prop = getProperties().get(name);
         if (prop == null) {
@@ -218,6 +228,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
     /**
      * @see org.apache.jackrabbit.webdav.DavResource#getProperties()
      */
+    @Override
     public DavPropertySet getProperties() {
         if (!initedProps) {
             initProperties();
@@ -232,6 +243,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      * @throws DavException Always throws {@link DavServletResponse#SC_METHOD_NOT_ALLOWED}
      * @see org.apache.jackrabbit.webdav.DavResource#setProperty(org.apache.jackrabbit.webdav.property.DavProperty)
      */
+    @Override
     public void setProperty(DavProperty<?> property) throws DavException {
         throw new DavException(DavServletResponse.SC_METHOD_NOT_ALLOWED);
     }
@@ -243,6 +255,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      * @throws DavException Always throws {@link DavServletResponse#SC_METHOD_NOT_ALLOWED}
      * @see org.apache.jackrabbit.webdav.DavResource#removeProperty(org.apache.jackrabbit.webdav.property.DavPropertyName)
      */
+    @Override
     public void removeProperty(DavPropertyName propertyName) throws DavException {
         throw new DavException(DavServletResponse.SC_METHOD_NOT_ALLOWED);
     }
@@ -252,6 +265,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      *
      * @see DavResource#alterProperties(List)
      */
+    @Override
     public MultiStatusResponse alterProperties(List<? extends PropEntry> changeList) throws DavException {
         throw new DavException(DavServletResponse.SC_METHOD_NOT_ALLOWED);
     }
@@ -263,6 +277,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      * @throws DavException Always throws {@link DavServletResponse#SC_METHOD_NOT_ALLOWED}
      * @see DavResource#move(org.apache.jackrabbit.webdav.DavResource)
      */
+    @Override
     public void move(DavResource destination) throws DavException {
         throw new DavException(DavServletResponse.SC_METHOD_NOT_ALLOWED);
     }
@@ -275,6 +290,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      * @throws DavException Always throws {@link DavServletResponse#SC_METHOD_NOT_ALLOWED}
      * @see DavResource#copy(org.apache.jackrabbit.webdav.DavResource, boolean)
      */
+    @Override
     public void copy(DavResource destination, boolean shallow) throws DavException {
         throw new DavException(DavServletResponse.SC_METHOD_NOT_ALLOWED);
     }
@@ -290,6 +306,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      * @return true if this resource may be locked by the given type and scope.
      * @see DavResource#isLockable(org.apache.jackrabbit.webdav.lock.Type, org.apache.jackrabbit.webdav.lock.Scope)
      */
+    @Override
     public boolean isLockable(Type type, Scope scope) {
         return supportedLock.isSupportedLock(type, scope);
     }
@@ -302,6 +319,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      * @return true if this resource has a lock applied with the given type and scope.
      * @see DavResource#hasLock(Type, Scope)
      */
+    @Override
     public boolean hasLock(Type type, Scope scope) {
         return getLock(type, scope) != null;
     }
@@ -309,6 +327,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
     /**
      * @see DavResource#getLock(Type, Scope)
      */
+    @Override
     public ActiveLock getLock(Type type, Scope scope) {
         ActiveLock lock = null;
         if (TransactionConstants.TRANSACTION.equals(type)) {
@@ -321,6 +340,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      * @see DavResource#getLocks()
      * todo improve....
      */
+    @Override
     public ActiveLock[] getLocks() {
         List<ActiveLock> locks = new ArrayList<ActiveLock>();
         // tx locks
@@ -348,6 +368,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
     /**
      * @see DavResource#lock(org.apache.jackrabbit.webdav.lock.LockInfo)
      */
+    @Override
     public ActiveLock lock(LockInfo reqLockInfo) throws DavException {
         if (isLockable(reqLockInfo.getType(), reqLockInfo.getScope())) {
             return txMgr.createLock(reqLockInfo, this);
@@ -364,6 +385,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      * @throws DavException
      * @see DavResource#refreshLock(org.apache.jackrabbit.webdav.lock.LockInfo, String)
      */
+    @Override
     public ActiveLock refreshLock(LockInfo info, String lockToken) throws DavException {
         return txMgr.refreshLock(info, lockToken, this);
     }
@@ -376,6 +398,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      * @param lockToken
      * @throws DavException Always throws {@link DavServletResponse#SC_METHOD_NOT_ALLOWED}
      */
+    @Override
     public void unlock(String lockToken) throws DavException {
         throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED);
     }
@@ -383,6 +406,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
     /**
      * @see DavResource#addLockManager(org.apache.jackrabbit.webdav.lock.LockManager)
      */
+    @Override
     public void addLockManager(LockManager lockMgr) {
         if (lockMgr instanceof TxLockManagerImpl) {
             txMgr = (TxLockManagerImpl) lockMgr;
@@ -392,6 +416,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
     /**
      * @see org.apache.jackrabbit.webdav.DavResource#getFactory()
      */
+    @Override
     public DavResourceFactory getFactory() {
         return factory;
     }
@@ -401,6 +426,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      * @see org.apache.jackrabbit.webdav.transaction.TransactionResource#getSession()
      * @see org.apache.jackrabbit.webdav.observation.ObservationResource#getSession()
      */
+    @Override
     public DavSession getSession() {
         return session;
     }
@@ -409,6 +435,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
     /**
      * @see TransactionResource#init(TxLockManager, String)
      */
+    @Override
     public void init(TxLockManager txMgr, String transactionId) {
         this.txMgr = (TxLockManagerImpl) txMgr;
         this.transactionId = transactionId;
@@ -417,6 +444,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
     /**
      * @see TransactionResource#unlock(String, org.apache.jackrabbit.webdav.transaction.TransactionInfo)
      */
+    @Override
     public void unlock(String lockToken, TransactionInfo tInfo) throws DavException {
         txMgr.releaseLock(tInfo, lockToken, this);
     }
@@ -424,6 +452,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
     /**
      * @see TransactionResource#getTransactionId()
      */
+    @Override
     public String getTransactionId() {
         return transactionId;
     }
@@ -434,6 +463,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      * @return object to be used in the OPTIONS response body or <code>null</code>
      * @see DeltaVResource#getOptionResponse(org.apache.jackrabbit.webdav.version.OptionsInfo)
      */
+    @Override
     public OptionsResponse getOptionResponse(OptionsInfo optionsInfo) {
         OptionsResponse oR = null;
         if (optionsInfo != null) {
@@ -460,6 +490,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      * @throws DavException
      * @see DeltaVResource#getReport(org.apache.jackrabbit.webdav.version.report.ReportInfo)
      */
+    @Override
     public Report getReport(ReportInfo reportInfo) throws DavException {
         if (reportInfo == null) {
             throw new DavException(DavServletResponse.SC_BAD_REQUEST, "A REPORT request must provide a valid XML request body.");
@@ -468,12 +499,18 @@ abstract class AbstractResource implements DavResource, TransactionResource,
             throw new DavException(DavServletResponse.SC_NOT_FOUND);
         }
 
-        if (supportedReports.isSupportedReport(reportInfo)) {
-            Report report = ReportType.getType(reportInfo).createReport(this, reportInfo);
-            return report;
-        } else {
-            throw new DavException(DavServletResponse.SC_UNPROCESSABLE_ENTITY, "Unknown report "+ reportInfo.getReportName() +"requested.");
+        if (!supportedReports.isSupportedReport(reportInfo)) {
+            Element condition = null;
+            try {
+                condition = DomUtil.createDocument().createElementNS("DAV:", "supported-report");
+            } catch (ParserConfigurationException ex) {
+                // we don't care THAT much
+            }
+            throw new DavException(DavServletResponse.SC_CONFLICT,
+                    "Unknown report '" + reportInfo.getReportName() + "' requested.", null, condition);
         }
+
+        return ReportType.getType(reportInfo).createReport(this, reportInfo);
     }
 
     /**
@@ -484,6 +521,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      * @throws DavException Always throws.
      * @see DeltaVResource#addWorkspace(org.apache.jackrabbit.webdav.DavResource)
      */
+    @Override
     public void addWorkspace(DavResource workspace) throws DavException {
         throw new DavException(DavServletResponse.SC_FORBIDDEN);
     }
@@ -497,10 +535,11 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      * @throws DavException
      * @see DeltaVResource#getReferenceResources(org.apache.jackrabbit.webdav.property.DavPropertyName)
      */
+    @Override
     public DavResource[] getReferenceResources(DavPropertyName hrefPropertyName) throws DavException {
         DavProperty<?> prop = getProperty(hrefPropertyName);
         if (prop == null || !(prop instanceof HrefProperty)) {
-            throw new DavException(DavServletResponse.SC_CONFLICT, "Unknown Href-Property '"+hrefPropertyName+"' on resource "+getResourcePath());
+            throw new DavException(DavServletResponse.SC_CONFLICT, "Unknown Href-Property '" + hrefPropertyName + "' on resource " + getResourcePath());
         }
 
         List<String> hrefs = ((HrefProperty)prop).getHrefs();
@@ -546,6 +585,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      * @return
      * @see org.apache.jackrabbit.webdav.search.SearchResource#getQueryGrammerSet()
      */
+    @Override
     public QueryGrammerSet getQueryGrammerSet() {
         return new SearchResourceImpl(getLocator(), session).getQueryGrammerSet();
     }
@@ -556,6 +596,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
      * @throws DavException
      * @see SearchResource#search(org.apache.jackrabbit.webdav.search.SearchInfo)
      */
+    @Override
     public MultiStatus search(SearchInfo sInfo) throws DavException {
         return new SearchResourceImpl(getLocator(), session).search(sInfo);
     }
@@ -768,6 +809,7 @@ abstract class AbstractResource implements DavResource, TransactionResource,
         /**
          * @see EventListener#onEvent(javax.jcr.observation.EventIterator)
          */
+        @Override
         public void onEvent(EventIterator events) {
             while (events.hasNext()) {
                 try {
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DavLocatorFactoryImpl.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DavLocatorFactoryImpl.java
index c8a97ce..391452c 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DavLocatorFactoryImpl.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DavLocatorFactoryImpl.java
@@ -62,7 +62,7 @@ public class DavLocatorFactoryImpl extends AbstractLocatorFactory {
                 return (repositoryPath.length() == 0) ? ItemResourceConstants.ROOT_ITEM_PATH : repositoryPath;
             } else {
                 log.error("Unexpected format of resource path.");
-                throw new IllegalArgumentException("Unexpected format of resource path.");
+                throw new IllegalArgumentException("Unexpected format of resource path: " + resourcePath + " (workspace: " + wspPath + ")");
             }
         }
     }
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DavResourceFactoryImpl.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DavResourceFactoryImpl.java
index 9cd51b0..79b867e 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DavResourceFactoryImpl.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DavResourceFactoryImpl.java
@@ -74,7 +74,7 @@ public class DavResourceFactoryImpl implements DavResourceFactory {
      * objects. Note, that in contrast to
      * {@link #createResource(DavResourceLocator, DavSession)} the locator may
      * point to a non-existing resource.
-     * <p/>
+     * <p>
      * If the request contains a {@link org.apache.jackrabbit.webdav.version.DeltaVServletRequest#getLabel()
      * Label header}, the resource is build from the indicated
      * {@link org.apache.jackrabbit.webdav.version.VersionResource version} instead.
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DefaultItemCollection.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DefaultItemCollection.java
index 786c658..47e90d3 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DefaultItemCollection.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DefaultItemCollection.java
@@ -62,6 +62,10 @@ import org.apache.jackrabbit.webdav.DavResourceLocator;
 import org.apache.jackrabbit.webdav.DavServletResponse;
 import org.apache.jackrabbit.webdav.MultiStatusResponse;
 import org.apache.jackrabbit.webdav.jcr.property.JcrDavPropertyNameSet;
+import org.apache.jackrabbit.webdav.jcr.security.JcrUserPrivilegesProperty;
+import org.apache.jackrabbit.webdav.jcr.security.JcrSupportedPrivilegesProperty;
+import org.apache.jackrabbit.webdav.jcr.security.SecurityUtils;
+import org.apache.jackrabbit.webdav.security.SecurityConstants;
 import org.apache.jackrabbit.webdav.util.HttpDateFormat;
 import org.apache.jackrabbit.webdav.io.InputContext;
 import org.apache.jackrabbit.webdav.io.OutputContext;
@@ -136,6 +140,7 @@ public class DefaultItemCollection extends AbstractItemResource
         }
     }
 
+    @Override
     public long getModificationTime() {
         // retrieve mod-time from jcr:lastmodified property if existing
         if (exists()) {
@@ -172,6 +177,7 @@ public class DefaultItemCollection extends AbstractItemResource
      * @return true
      * @see org.apache.jackrabbit.webdav.DavResource#isCollection()
      */
+    @Override
     public boolean isCollection() {
         return true;
     }
@@ -228,6 +234,10 @@ public class DefaultItemCollection extends AbstractItemResource
                 } else if (OrderingConstants.ORDERING_TYPE.equals(name) && isOrderable()) {
                     // property defined by RFC 3648: this resource always has custom ordering!                    
                     prop = new OrderingType(OrderingConstants.ORDERING_TYPE_CUSTOM);
+                } else if (SecurityConstants.SUPPORTED_PRIVILEGE_SET.equals(name)) {
+                    prop = new JcrSupportedPrivilegesProperty(getRepositorySession(), n.getPath()).asDavProperty();
+                } else if (SecurityConstants.CURRENT_USER_PRIVILEGE_SET.equals(name)) {
+                    prop = new JcrUserPrivilegesProperty(getRepositorySession(), n.getPath()).asDavProperty();
                 }
             } catch (RepositoryException e) {
                 log.error("Failed to retrieve node-specific property: " + e);
@@ -399,7 +409,7 @@ public class DefaultItemCollection extends AbstractItemResource
      * added} to the item represented by this resource. If an input stream is specified
      * together with a collection resource {@link Session#importXML(String, java.io.InputStream, int)}
      * is called instead and this resource path is used as <code>parentAbsPath</code> argument.
-     * <p/>
+     * <p>
      * However, if the specified resource is not of resource type collection a
      * new {@link Property} is set or an existing one is changed by modifying its
      * value.<br>
@@ -416,6 +426,7 @@ public class DefaultItemCollection extends AbstractItemResource
      * @see Node#addNode(String)
      * @see Node#setProperty(String, java.io.InputStream)
      */
+    @Override
     public void addMember(DavResource resource, InputContext inputContext)
             throws DavException {
 
@@ -524,6 +535,7 @@ public class DefaultItemCollection extends AbstractItemResource
     /**
      * @see org.apache.jackrabbit.webdav.DavResource#getMembers()
      */
+    @Override
     public DavResourceIterator getMembers() {
         ArrayList<DavResource> memberList = new ArrayList<DavResource>();
         if (exists()) {
@@ -563,6 +575,7 @@ public class DefaultItemCollection extends AbstractItemResource
      * @see DavResource#removeMember(DavResource)
      * @see javax.jcr.Item#remove()
      */
+    @Override
     public void removeMember(DavResource member) throws DavException {
         Session session = getRepositorySession();
         try {
@@ -626,6 +639,12 @@ public class DefaultItemCollection extends AbstractItemResource
                 } else if (((Node) item).isLocked()) {
                     Lock jcrLock = ((Node) item).getLock();
                     lock = new JcrActiveLock(jcrLock);
+                    DavResourceLocator locator = super.getLocator();
+                    String lockroot = locator
+                            .getFactory()
+                            .createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(), jcrLock.getNode().getPath(),
+                                    false).getHref(false);
+                    lock.setLockroot(lockroot);
                 }
             } catch (AccessDeniedException e) {
                 log.error("Error while accessing resource lock: "+e.getMessage());
@@ -666,7 +685,15 @@ public class DefaultItemCollection extends AbstractItemResource
             }
             try {
                 boolean sessionScoped = EXCLUSIVE_SESSION.equals(reqLockInfo.getScope());
-                Lock jcrLock = ((Node)item).lock(reqLockInfo.isDeep(), sessionScoped);
+                long timeout = reqLockInfo.getTimeout();
+                if (timeout == LockInfo.INFINITE_TIMEOUT) {
+                    timeout = Long.MAX_VALUE;
+                } else {
+                    timeout = timeout/1000;
+                }
+                javax.jcr.lock.LockManager lockMgr = getRepositorySession().getWorkspace().getLockManager();
+                Lock jcrLock = lockMgr.lock((item).getPath(), reqLockInfo.isDeep(),
+                        sessionScoped, timeout, reqLockInfo.getOwner());
                 ActiveLock lock = new JcrActiveLock(jcrLock);
                  // add reference to DAVSession for this lock
                 getSession().addReference(lock.getToken());
@@ -779,6 +806,7 @@ public class DefaultItemCollection extends AbstractItemResource
      * @see org.apache.jackrabbit.webdav.ordering.OrderingResource#isOrderable()
      * @see javax.jcr.nodetype.NodeType#hasOrderableChildNodes()
      */
+    @Override
     public boolean isOrderable() {
         boolean orderable = false;
         if (exists()) {
@@ -800,6 +828,7 @@ public class DefaultItemCollection extends AbstractItemResource
      * @see org.apache.jackrabbit.webdav.ordering.OrderingResource#orderMembers(org.apache.jackrabbit.webdav.ordering.OrderPatch)
      * @see Node#orderBefore(String, String)
      */
+    @Override
     public void orderMembers(OrderPatch orderPatch) throws DavException {
         if (!isOrderable()) {
             throw new DavException(DavServletResponse.SC_METHOD_NOT_ALLOWED);
@@ -932,6 +961,10 @@ public class DefaultItemCollection extends AbstractItemResource
             if (isOrderable()) {
                 names.add(OrderingConstants.ORDERING_TYPE);
             }
+            if (SecurityUtils.supportsAccessControl(getRepositorySession())) {
+                names.add(SecurityConstants.SUPPORTED_PRIVILEGE_SET);
+                names.add(SecurityConstants.CURRENT_USER_PRIVILEGE_SET);
+            }
         }
     }
                                               
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DefaultItemResource.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DefaultItemResource.java
index ee0d8a9..df9db1a 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DefaultItemResource.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DefaultItemResource.java
@@ -95,6 +95,7 @@ public class DefaultItemResource extends AbstractItemResource {
      * @return false
      * @see DavResource#isCollection()
      */
+    @Override
     public boolean isCollection() {
         return false;
     }
@@ -105,6 +106,7 @@ public class DefaultItemResource extends AbstractItemResource {
      * @return
      * @see DavResource#getModificationTime()
      */
+    @Override
     public long getModificationTime() {
         return new Date().getTime();
     }
@@ -302,6 +304,7 @@ public class DefaultItemResource extends AbstractItemResource {
      *
      * @see org.apache.jackrabbit.webdav.DavResource#addMember(org.apache.jackrabbit.webdav.DavResource, InputContext)
      */
+    @Override
     public void addMember(DavResource resource, InputContext inputContext) throws DavException {
         throw new DavException(DavServletResponse.SC_METHOD_NOT_ALLOWED, "Cannot add members to a non-collection resource");
     }
@@ -313,6 +316,7 @@ public class DefaultItemResource extends AbstractItemResource {
      * @return an empty iterator
      * @see DavResource#getMembers()
      */
+    @Override
     public DavResourceIterator getMembers() {
         log.warn("A non-collection resource never has internal members.");
         List<DavResource> drl = Collections.emptyList();
@@ -324,6 +328,7 @@ public class DefaultItemResource extends AbstractItemResource {
      *
      * @see DavResource#removeMember(DavResource)
      */
+    @Override
     public void removeMember(DavResource member) throws DavException {
         throw new DavException(DavServletResponse.SC_METHOD_NOT_ALLOWED, "Cannot remove members from a non-collection resource");
     }
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/EventJournalResourceImpl.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/EventJournalResourceImpl.java
index a37fc27..868ee32 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/EventJournalResourceImpl.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/EventJournalResourceImpl.java
@@ -1,473 +1,483 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.webdav.jcr;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.UnsupportedRepositoryOperationException;
-import javax.jcr.observation.Event;
-import javax.jcr.observation.EventJournal;
-import javax.servlet.http.HttpServletRequest;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.sax.SAXTransformerFactory;
-import javax.xml.transform.sax.TransformerHandler;
-import javax.xml.transform.stream.StreamResult;
-
-import org.apache.jackrabbit.commons.webdav.AtomFeedConstants;
-import org.apache.jackrabbit.commons.webdav.EventUtil;
-import org.apache.jackrabbit.spi.Name;
-import org.apache.jackrabbit.spi.commons.AdditionalEventInfo;
-import org.apache.jackrabbit.util.ISO8601;
-import org.apache.jackrabbit.webdav.DavConstants;
-import org.apache.jackrabbit.webdav.DavException;
-import org.apache.jackrabbit.webdav.DavResource;
-import org.apache.jackrabbit.webdav.DavResourceFactory;
-import org.apache.jackrabbit.webdav.DavResourceIterator;
-import org.apache.jackrabbit.webdav.DavResourceIteratorImpl;
-import org.apache.jackrabbit.webdav.DavResourceLocator;
-import org.apache.jackrabbit.webdav.DavServletResponse;
-import org.apache.jackrabbit.webdav.io.InputContext;
-import org.apache.jackrabbit.webdav.io.OutputContext;
-import org.apache.jackrabbit.webdav.observation.ObservationConstants;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.AttributesImpl;
-
-/**
- * Implements a JCR {@link EventJournal} in terms of an RFC 4287 Atom feed.
- * <p>
- * Each feed entry represents either a single event, or, if the repository
- * supports the {@link Event#PERSIST} event, an event bundle. The actual event
- * data is sent in the Atom <content> element and uses the same XML
- * serialization as the one used for subscriptions.
- * <p>
- * Skipping is implemented by specifying the desired time offset (represented
- * as hexadecimal long in ms since the epoch) disguised as ETag in the HTTP "If-None-Match" 
- * header field.
- * <p>
- * The generated feed may not be complete; the total number of events is limited in
- * order not to overload the client.
- * <p>
- * Furthermore, the number of events is limited by going up to 2000 ms into the future
- * (based on the request time). This is supposed to limit the wait time for the client).
- */
-public class EventJournalResourceImpl extends AbstractResource {
-
-    public static final String RELURIFROMWORKSPACE = "?type=journal";
-    
-    public static final String EVENTMEDIATYPE = "application/vnd.apache.jackrabbit.event+xml";
-
-    private static Logger log = LoggerFactory.getLogger(EventJournalResourceImpl.class);
-
-    private final HttpServletRequest request;
-    private final EventJournal journal;
-    private final DavResourceLocator locator;
-
-    EventJournalResourceImpl(EventJournal journal, DavResourceLocator locator, JcrDavSession session,
-            HttpServletRequest request, DavResourceFactory factory) {
-        super(locator, session, factory);
-        this.journal = journal;
-        this.locator = locator;
-        this.request = request;
-    }
-
-    public String getSupportedMethods() {
-        return "GET, HEAD";
-    }
-
-    public boolean exists() {
-        try {
-            List<String> available = Arrays.asList(getRepositorySession().getWorkspace().getAccessibleWorkspaceNames());
-            return available.contains(getLocator().getWorkspaceName());
-        } catch (RepositoryException e) {
-            log.warn(e.getMessage());
-            return false;
-        }
-    }
-
-    public boolean isCollection() {
-        return false;
-    }
-
-    public String getDisplayName() {
-        return "event journal for " + getLocator().getWorkspaceName();
-    }
-
-    public long getModificationTime() {
-        return System.currentTimeMillis();
-    }
-
-    private static final String ATOMNS = AtomFeedConstants.NS_URI;
-    private static final String EVNS = ObservationConstants.NAMESPACE.getURI();
-
-    private static final String AUTHOR = AtomFeedConstants.XML_AUTHOR;
-    private static final String CONTENT = AtomFeedConstants.XML_CONTENT;
-    private static final String ENTRY = AtomFeedConstants.XML_ENTRY;
-    private static final String FEED = AtomFeedConstants.XML_FEED;
-    private static final String ID = AtomFeedConstants.XML_ID;
-    private static final String LINK = AtomFeedConstants.XML_LINK;
-    private static final String NAME = AtomFeedConstants.XML_NAME;
-    private static final String TITLE = AtomFeedConstants.XML_TITLE;
-    private static final String UPDATED = AtomFeedConstants.XML_UPDATED;
-
-    private static final String E_EVENT = ObservationConstants.XML_EVENT;
-    private static final String E_EVENTDATE = ObservationConstants.XML_EVENTDATE;
-    private static final String E_EVENTIDENTIFIER = ObservationConstants.XML_EVENTIDENTIFIER;
-    private static final String E_EVENTINFO = ObservationConstants.XML_EVENTINFO;
-    private static final String E_EVENTTYPE = ObservationConstants.XML_EVENTTYPE;
-    private static final String E_EVENTMIXINNODETYPE = ObservationConstants.XML_EVENTMIXINNODETYPE;
-    private static final String E_EVENTPRIMARNODETYPE = ObservationConstants.XML_EVENTPRIMARNODETYPE;
-    private static final String E_EVENTUSERDATA = ObservationConstants.XML_EVENTUSERDATA;
-
-    private static final int MAXWAIT = 2000; // maximal wait time
-    private static final int MAXEV = 10000; // maximal event number
-
-    private static final Attributes NOATTRS = new AttributesImpl();
-
-    public void spool(OutputContext outputContext) throws IOException {
-
-        Calendar cal = Calendar.getInstance(Locale.ENGLISH);
-
-        try {
-            outputContext.setContentType("application/atom+xml; charset=UTF-8");
-            outputContext.setProperty("Vary", "If-None-Match");
-            // TODO: Content-Encoding: gzip
-
-            // find out where to start
-            long prevts = -1;
-            String inm = request.getHeader("If-None-Match");
-            if (inm != null) {
-                // TODO: proper parsing when comma-delimited
-                inm = inm.trim();
-                if (inm.startsWith("\"") && inm.endsWith("\"")) {
-                    String tmp = inm.substring(1, inm.length() - 1);
-                    try {
-                        prevts = Long.parseLong(tmp, 16);
-                        journal.skipTo(prevts);
-                    } catch (NumberFormatException ex) {
-                        // broken etag
-                    }
-                }
-            }
-
-            boolean hasPersistEvents = false;
-
-            if (outputContext.hasStream()) {
-
-                long lastts = -1;
-                long now = System.currentTimeMillis();
-                boolean done = false;
-
-                // collect events
-                List<Event> events = new ArrayList<Event>(MAXEV);
-
-                while (!done && journal.hasNext()) {
-                    Event e = journal.nextEvent();
-
-                    hasPersistEvents |= e.getType() == Event.PERSIST;
-
-                    if (e.getDate() != lastts) {
-                        // consider stopping
-                        if (events.size() > MAXEV) {
-                            done = true;
-                        }
-                        if (e.getDate() > now + MAXWAIT) {
-                            done = true;
-                        }
-                    }
-
-                    if (!done && (prevts == -1 || e.getDate() >= prevts)) {
-                        events.add(e);
-                    }
-
-                    lastts = e.getDate();
-                }
-
-                if (lastts >= 0) {
-                    // construct ETag from newest event
-                    outputContext.setETag("\"" + Long.toHexString(lastts) + "\"");
-                }
-
-                OutputStream os = outputContext.getOutputStream();
-                StreamResult streamResult = new StreamResult(os);
-                SAXTransformerFactory tf = (SAXTransformerFactory) TransformerFactory.newInstance();
-                TransformerHandler th = tf.newTransformerHandler();
-                Transformer s = th.getTransformer();
-                s.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
-                s.setOutputProperty(OutputKeys.INDENT, "yes");
-                s.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
-
-                th.setResult(streamResult);
-
-                th.startDocument();
-
-                th.startElement(ATOMNS, FEED, FEED, NOATTRS);
-
-                writeAtomElement(th, TITLE, "EventJournal for " + getLocator().getWorkspaceName());
-
-                th.startElement(ATOMNS, AUTHOR, AUTHOR, NOATTRS);
-                writeAtomElement(th, NAME, "Jackrabbit Event Journal Feed Generator");
-                th.endElement(ATOMNS, AUTHOR, AUTHOR);
-
-                String id = getFullUri(request);
-                writeAtomElement(th, ID, id);
-
-                AttributesImpl linkattrs = new AttributesImpl();
-                linkattrs.addAttribute(null, "self", "self", "CDATA", id);
-                writeAtomElement(th, LINK, linkattrs, null);
-
-                cal.setTimeInMillis(lastts >= 0 ? lastts : now);
-                String upd = ISO8601.format(cal);
-                writeAtomElement(th, UPDATED, upd);
-
-                String lastDateString = "";
-                long lastTimeStamp = 0;
-                long index = 0;
-
-                AttributesImpl contentatt = new AttributesImpl();
-                contentatt.addAttribute(null, "type", "type", "CDATA", EVENTMEDIATYPE);
-
-                while (!events.isEmpty()) {
-
-                    List<Event> bundle = null;
-                    String path = null;
-                    String op;
-
-                    if (hasPersistEvents) {
-                        bundle = new ArrayList<Event>();
-                        Event e = null;
-                        op = "operations";
-
-                        do {
-                            e = events.remove(0);
-                            bundle.add(e);
-
-                            // compute common path
-                            if (path == null) {
-                                path = e.getPath();
-                            } else {
-                                if (e.getPath() != null && e.getPath().length() < path.length()) {
-                                    path = e.getPath();
-                                }
-                            }
-                        } while (e.getType() != Event.PERSIST && !events.isEmpty());
-                    } else {
-                        // no persist events
-                        Event e = events.remove(0);
-                        bundle = Collections.singletonList(e);
-                        path = e.getPath();
-                        op = EventUtil.getEventName(e.getType());
-                    }
-
-                    Event firstEvent = bundle.get(0);
-
-                    String entryupd = lastDateString;
-                    if (lastTimeStamp != firstEvent.getDate()) {
-                        cal.setTimeInMillis(firstEvent.getDate());
-                        entryupd = ISO8601.format(cal);
-                        index = 0;
-                    } else {
-                        index += 1;
-                    }
-
-                    th.startElement(ATOMNS, ENTRY, ENTRY, NOATTRS);
-
-                    String entrytitle = op + (path != null ? (": " + path) : "");
-                    writeAtomElement(th, TITLE, entrytitle);
-
-                    String entryid = id + "?type=journal&ts=" + Long.toHexString(firstEvent.getDate()) + "-" + index;
-                    writeAtomElement(th, ID, entryid);
-
-                    String author = firstEvent.getUserID() == null || firstEvent.getUserID().length() == 0 ? null
-                            : firstEvent.getUserID();
-                    if (author != null) {
-                        th.startElement(ATOMNS, AUTHOR, AUTHOR, NOATTRS);
-                        writeAtomElement(th, NAME, author);
-                        th.endElement(ATOMNS, AUTHOR, AUTHOR);
-                    }
-
-                    writeAtomElement(th, UPDATED, entryupd);
-
-                    th.startElement(ATOMNS, CONTENT, CONTENT, contentatt);
-
-                    for (Event e : bundle) {
-
-                        // serialize the event
-                        th.startElement(EVNS, E_EVENT, E_EVENT, NOATTRS);
-
-                        // DAV:href
-                        if (e.getPath() != null) {
-                            boolean isCollection = (e.getType() == Event.NODE_ADDED || e.getType() == Event.NODE_REMOVED);
-                            String href = locator
-                                    .getFactory()
-                                    .createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(),
-                                            e.getPath(), false).getHref(isCollection);
-                            th.startElement(DavConstants.NAMESPACE.getURI(), DavConstants.XML_HREF,
-                                    DavConstants.XML_HREF, NOATTRS);
-                            th.characters(href.toCharArray(), 0, href.length());
-                            th.endElement(DavConstants.NAMESPACE.getURI(), DavConstants.XML_HREF, DavConstants.XML_HREF);
-                        }
-
-                        // event type
-                        String evname = EventUtil.getEventName(e.getType());
-                        th.startElement(EVNS, E_EVENTTYPE, E_EVENTTYPE, NOATTRS);
-                        th.startElement(EVNS, evname, evname, NOATTRS);
-                        th.endElement(EVNS, evname, evname);
-                        th.endElement(EVNS, E_EVENTTYPE, E_EVENTTYPE);
-
-                        // date
-                        writeObsElement(th, E_EVENTDATE, Long.toString(e.getDate()));
-
-                        // user data
-                        if (e.getUserData() != null && e.getUserData().length() > 0) {
-                            writeObsElement(th, E_EVENTUSERDATA, firstEvent.getUserData());
-                        }
-
-                        // user id: already sent as Atom author/name
-
-                        // try to compute nodetype information
-                        if (e instanceof AdditionalEventInfo) {
-                            try {
-                                Name pnt = ((AdditionalEventInfo) e).getPrimaryNodeTypeName();
-                                if (pnt != null) {
-                                    writeObsElement(th, E_EVENTPRIMARNODETYPE, pnt.toString());
-                                }
-
-                                Set<Name> mixins = ((AdditionalEventInfo) e).getMixinTypeNames();
-                                if (mixins != null) {
-                                    for (Name mixin : mixins) {
-                                        writeObsElement(th, E_EVENTMIXINNODETYPE, mixin.toString());
-                                    }
-                                }
-
-                            } catch (UnsupportedRepositoryOperationException ex) {
-                                // optional
-                            }
-                        }
-
-                        // identifier
-                        if (e.getIdentifier() != null) {
-                            writeObsElement(th, E_EVENTIDENTIFIER, e.getIdentifier());
-                        }
-
-                        // info
-                        if (!e.getInfo().isEmpty()) {
-                            th.startElement(EVNS, E_EVENTINFO, E_EVENTINFO, NOATTRS);
-                            Map<?, ?> m = e.getInfo();
-                            for (Map.Entry<?, ?> entry : m.entrySet()) {
-                                String key = entry.getKey().toString();
-                                Object value = entry.getValue();
-                                String t = value != null ? value.toString() : null;
-                                writeElement(th, null, key, NOATTRS, t);
-                            }
-                            th.endElement(EVNS, E_EVENTINFO, E_EVENTINFO);
-                        }
-
-                        th.endElement(EVNS, E_EVENT, E_EVENT);
-
-                        lastTimeStamp = e.getDate();
-                        lastDateString = entryupd;
-                    }
-
-                    th.endElement(ATOMNS, CONTENT, CONTENT);
-                    th.endElement(ATOMNS, ENTRY, ENTRY);
-                }
-
-                th.endElement(ATOMNS, FEED, FEED);
-
-                th.endDocument();
-
-                os.flush();
-            }
-        } catch (Exception ex) {
-            throw new IOException("error generating feed: " + ex.getMessage());
-        }
-    }
-
-    public DavResource getCollection() {
-        return null;
-    }
-
-    public void addMember(DavResource resource, InputContext inputContext) throws DavException {
-        throw new DavException(DavServletResponse.SC_FORBIDDEN);
-    }
-
-    public DavResourceIterator getMembers() {
-        return DavResourceIteratorImpl.EMPTY;
-    }
-
-    public void removeMember(DavResource member) throws DavException {
-        throw new DavException(DavServletResponse.SC_FORBIDDEN);
-    }
-
-    @Override
-    protected void initLockSupport() {
-        // lock not allowed
-    }
-
-    @Override
-    protected String getWorkspaceHref() {
-        return getHref();
-    }
-
-    private void writeElement(TransformerHandler th, String ns, String name, Attributes attrs, String textContent)
-            throws SAXException {
-        th.startElement(ns, name, name, attrs);
-        if (textContent != null) {
-            th.characters(textContent.toCharArray(), 0, textContent.length());
-        }
-        th.endElement(ns, name, name);
-    }
-
-    private void writeAtomElement(TransformerHandler th, String name, Attributes attrs, String textContent)
-            throws SAXException {
-        writeElement(th, ATOMNS, name, attrs, textContent);
-    }
-
-    private void writeAtomElement(TransformerHandler th, String name, String textContent) throws SAXException {
-        writeAtomElement(th, name, NOATTRS, textContent);
-    }
-
-    private void writeObsElement(TransformerHandler th, String name, String textContent) throws SAXException {
-        writeElement(th, EVNS, name, NOATTRS, textContent);
-    }
-
-    private String getFullUri(HttpServletRequest req) {
-
-        String scheme = req.getScheme();
-        int port = req.getServerPort();
-        boolean isDefaultPort = (scheme.equals("http") && port == 80) || (scheme.equals("http") && port == 443);
-        String query = request.getQueryString() != null ? "?" + request.getQueryString() : "";
-
-        return String.format("%s://%s%s%s%s%s", scheme, req.getServerName(), isDefaultPort ? ":" : "",
-                isDefaultPort ? Integer.toString(port) : "", req.getRequestURI(), query);
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.jcr;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventJournal;
+import javax.servlet.http.HttpServletRequest;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.jackrabbit.commons.webdav.AtomFeedConstants;
+import org.apache.jackrabbit.commons.webdav.EventUtil;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.AdditionalEventInfo;
+import org.apache.jackrabbit.util.ISO8601;
+import org.apache.jackrabbit.webdav.DavConstants;
+import org.apache.jackrabbit.webdav.DavException;
+import org.apache.jackrabbit.webdav.DavResource;
+import org.apache.jackrabbit.webdav.DavResourceFactory;
+import org.apache.jackrabbit.webdav.DavResourceIterator;
+import org.apache.jackrabbit.webdav.DavResourceIteratorImpl;
+import org.apache.jackrabbit.webdav.DavResourceLocator;
+import org.apache.jackrabbit.webdav.DavServletResponse;
+import org.apache.jackrabbit.webdav.io.InputContext;
+import org.apache.jackrabbit.webdav.io.OutputContext;
+import org.apache.jackrabbit.webdav.observation.ObservationConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * Implements a JCR {@link EventJournal} in terms of an RFC 4287 Atom feed.
+ * <p>
+ * Each feed entry represents either a single event, or, if the repository
+ * supports the {@link Event#PERSIST} event, an event bundle. The actual event
+ * data is sent in the Atom <content> element and uses the same XML
+ * serialization as the one used for subscriptions.
+ * <p>
+ * Skipping is implemented by specifying the desired time offset (represented
+ * as hexadecimal long in ms since the epoch) disguised as ETag in the HTTP "If-None-Match" 
+ * header field.
+ * <p>
+ * The generated feed may not be complete; the total number of events is limited in
+ * order not to overload the client.
+ * <p>
+ * Furthermore, the number of events is limited by going up to 2000 ms into the future
+ * (based on the request time). This is supposed to limit the wait time for the client).
+ */
+public class EventJournalResourceImpl extends AbstractResource {
+
+    public static final String RELURIFROMWORKSPACE = "?type=journal";
+    
+    public static final String EVENTMEDIATYPE = "application/vnd.apache.jackrabbit.event+xml";
+
+    private static Logger log = LoggerFactory.getLogger(EventJournalResourceImpl.class);
+
+    private final HttpServletRequest request;
+    private final EventJournal journal;
+    private final DavResourceLocator locator;
+
+    EventJournalResourceImpl(EventJournal journal, DavResourceLocator locator, JcrDavSession session,
+            HttpServletRequest request, DavResourceFactory factory) {
+        super(locator, session, factory);
+        this.journal = journal;
+        this.locator = locator;
+        this.request = request;
+    }
+
+    @Override
+    public String getSupportedMethods() {
+        return "GET, HEAD";
+    }
+
+    @Override
+    public boolean exists() {
+        try {
+            List<String> available = Arrays.asList(getRepositorySession().getWorkspace().getAccessibleWorkspaceNames());
+            return available.contains(getLocator().getWorkspaceName());
+        } catch (RepositoryException e) {
+            log.warn(e.getMessage());
+            return false;
+        }
+    }
+
+    @Override
+    public boolean isCollection() {
+        return false;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "event journal for " + getLocator().getWorkspaceName();
+    }
+
+    @Override
+    public long getModificationTime() {
+        return System.currentTimeMillis();
+    }
+
+    private static final String ATOMNS = AtomFeedConstants.NS_URI;
+    private static final String EVNS = ObservationConstants.NAMESPACE.getURI();
+
+    private static final String AUTHOR = AtomFeedConstants.XML_AUTHOR;
+    private static final String CONTENT = AtomFeedConstants.XML_CONTENT;
+    private static final String ENTRY = AtomFeedConstants.XML_ENTRY;
+    private static final String FEED = AtomFeedConstants.XML_FEED;
+    private static final String ID = AtomFeedConstants.XML_ID;
+    private static final String LINK = AtomFeedConstants.XML_LINK;
+    private static final String NAME = AtomFeedConstants.XML_NAME;
+    private static final String TITLE = AtomFeedConstants.XML_TITLE;
+    private static final String UPDATED = AtomFeedConstants.XML_UPDATED;
+
+    private static final String E_EVENT = ObservationConstants.XML_EVENT;
+    private static final String E_EVENTDATE = ObservationConstants.XML_EVENTDATE;
+    private static final String E_EVENTIDENTIFIER = ObservationConstants.XML_EVENTIDENTIFIER;
+    private static final String E_EVENTINFO = ObservationConstants.XML_EVENTINFO;
+    private static final String E_EVENTTYPE = ObservationConstants.XML_EVENTTYPE;
+    private static final String E_EVENTMIXINNODETYPE = ObservationConstants.XML_EVENTMIXINNODETYPE;
+    private static final String E_EVENTPRIMARNODETYPE = ObservationConstants.XML_EVENTPRIMARNODETYPE;
+    private static final String E_EVENTUSERDATA = ObservationConstants.XML_EVENTUSERDATA;
+
+    private static final int MAXWAIT = 2000; // maximal wait time
+    private static final int MAXEV = 10000; // maximal event number
+
+    private static final Attributes NOATTRS = new AttributesImpl();
+
+    @Override
+    public void spool(OutputContext outputContext) throws IOException {
+
+        Calendar cal = Calendar.getInstance(Locale.ENGLISH);
+
+        try {
+            outputContext.setContentType("application/atom+xml; charset=UTF-8");
+            outputContext.setProperty("Vary", "If-None-Match");
+            // TODO: Content-Encoding: gzip
+
+            // find out where to start
+            long prevts = -1;
+            String inm = request.getHeader("If-None-Match");
+            if (inm != null) {
+                // TODO: proper parsing when comma-delimited
+                inm = inm.trim();
+                if (inm.startsWith("\"") && inm.endsWith("\"")) {
+                    String tmp = inm.substring(1, inm.length() - 1);
+                    try {
+                        prevts = Long.parseLong(tmp, 16);
+                        journal.skipTo(prevts);
+                    } catch (NumberFormatException ex) {
+                        // broken etag
+                    }
+                }
+            }
+
+            boolean hasPersistEvents = false;
+
+            if (outputContext.hasStream()) {
+
+                long lastts = -1;
+                long now = System.currentTimeMillis();
+                boolean done = false;
+
+                // collect events
+                List<Event> events = new ArrayList<Event>(MAXEV);
+
+                while (!done && journal.hasNext()) {
+                    Event e = journal.nextEvent();
+
+                    hasPersistEvents |= e.getType() == Event.PERSIST;
+
+                    if (e.getDate() != lastts) {
+                        // consider stopping
+                        if (events.size() > MAXEV) {
+                            done = true;
+                        }
+                        if (e.getDate() > now + MAXWAIT) {
+                            done = true;
+                        }
+                    }
+
+                    if (!done && (prevts == -1 || e.getDate() >= prevts)) {
+                        events.add(e);
+                    }
+
+                    lastts = e.getDate();
+                }
+
+                if (lastts >= 0) {
+                    // construct ETag from newest event
+                    outputContext.setETag("\"" + Long.toHexString(lastts) + "\"");
+                }
+
+                OutputStream os = outputContext.getOutputStream();
+                StreamResult streamResult = new StreamResult(os);
+                SAXTransformerFactory tf = (SAXTransformerFactory) TransformerFactory.newInstance();
+                TransformerHandler th = tf.newTransformerHandler();
+                Transformer s = th.getTransformer();
+                s.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+                s.setOutputProperty(OutputKeys.INDENT, "yes");
+                s.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+
+                th.setResult(streamResult);
+
+                th.startDocument();
+
+                th.startElement(ATOMNS, FEED, FEED, NOATTRS);
+
+                writeAtomElement(th, TITLE, "EventJournal for " + getLocator().getWorkspaceName());
+
+                th.startElement(ATOMNS, AUTHOR, AUTHOR, NOATTRS);
+                writeAtomElement(th, NAME, "Jackrabbit Event Journal Feed Generator");
+                th.endElement(ATOMNS, AUTHOR, AUTHOR);
+
+                String id = getFullUri(request);
+                writeAtomElement(th, ID, id);
+
+                AttributesImpl linkattrs = new AttributesImpl();
+                linkattrs.addAttribute(null, "self", "self", "CDATA", id);
+                writeAtomElement(th, LINK, linkattrs, null);
+
+                cal.setTimeInMillis(lastts >= 0 ? lastts : now);
+                String upd = ISO8601.format(cal);
+                writeAtomElement(th, UPDATED, upd);
+
+                String lastDateString = "";
+                long lastTimeStamp = 0;
+                long index = 0;
+
+                AttributesImpl contentatt = new AttributesImpl();
+                contentatt.addAttribute(null, "type", "type", "CDATA", EVENTMEDIATYPE);
+
+                while (!events.isEmpty()) {
+
+                    List<Event> bundle = null;
+                    String path = null;
+                    String op;
+
+                    if (hasPersistEvents) {
+                        bundle = new ArrayList<Event>();
+                        Event e = null;
+                        op = "operations";
+
+                        do {
+                            e = events.remove(0);
+                            bundle.add(e);
+
+                            // compute common path
+                            if (path == null) {
+                                path = e.getPath();
+                            } else {
+                                if (e.getPath() != null && e.getPath().length() < path.length()) {
+                                    path = e.getPath();
+                                }
+                            }
+                        } while (e.getType() != Event.PERSIST && !events.isEmpty());
+                    } else {
+                        // no persist events
+                        Event e = events.remove(0);
+                        bundle = Collections.singletonList(e);
+                        path = e.getPath();
+                        op = EventUtil.getEventName(e.getType());
+                    }
+
+                    Event firstEvent = bundle.get(0);
+
+                    String entryupd = lastDateString;
+                    if (lastTimeStamp != firstEvent.getDate()) {
+                        cal.setTimeInMillis(firstEvent.getDate());
+                        entryupd = ISO8601.format(cal);
+                        index = 0;
+                    } else {
+                        index += 1;
+                    }
+
+                    th.startElement(ATOMNS, ENTRY, ENTRY, NOATTRS);
+
+                    String entrytitle = op + (path != null ? (": " + path) : "");
+                    writeAtomElement(th, TITLE, entrytitle);
+
+                    String entryid = id + "?type=journal&ts=" + Long.toHexString(firstEvent.getDate()) + "-" + index;
+                    writeAtomElement(th, ID, entryid);
+
+                    String author = firstEvent.getUserID() == null || firstEvent.getUserID().length() == 0 ? null
+                            : firstEvent.getUserID();
+                    if (author != null) {
+                        th.startElement(ATOMNS, AUTHOR, AUTHOR, NOATTRS);
+                        writeAtomElement(th, NAME, author);
+                        th.endElement(ATOMNS, AUTHOR, AUTHOR);
+                    }
+
+                    writeAtomElement(th, UPDATED, entryupd);
+
+                    th.startElement(ATOMNS, CONTENT, CONTENT, contentatt);
+
+                    for (Event e : bundle) {
+
+                        // serialize the event
+                        th.startElement(EVNS, E_EVENT, E_EVENT, NOATTRS);
+
+                        // DAV:href
+                        if (e.getPath() != null) {
+                            boolean isCollection = (e.getType() == Event.NODE_ADDED || e.getType() == Event.NODE_REMOVED);
+                            String href = locator
+                                    .getFactory()
+                                    .createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(),
+                                            e.getPath(), false).getHref(isCollection);
+                            th.startElement(DavConstants.NAMESPACE.getURI(), DavConstants.XML_HREF,
+                                    DavConstants.XML_HREF, NOATTRS);
+                            th.characters(href.toCharArray(), 0, href.length());
+                            th.endElement(DavConstants.NAMESPACE.getURI(), DavConstants.XML_HREF, DavConstants.XML_HREF);
+                        }
+
+                        // event type
+                        String evname = EventUtil.getEventName(e.getType());
+                        th.startElement(EVNS, E_EVENTTYPE, E_EVENTTYPE, NOATTRS);
+                        th.startElement(EVNS, evname, evname, NOATTRS);
+                        th.endElement(EVNS, evname, evname);
+                        th.endElement(EVNS, E_EVENTTYPE, E_EVENTTYPE);
+
+                        // date
+                        writeObsElement(th, E_EVENTDATE, Long.toString(e.getDate()));
+
+                        // user data
+                        if (e.getUserData() != null && e.getUserData().length() > 0) {
+                            writeObsElement(th, E_EVENTUSERDATA, firstEvent.getUserData());
+                        }
+
+                        // user id: already sent as Atom author/name
+
+                        // try to compute nodetype information
+                        if (e instanceof AdditionalEventInfo) {
+                            try {
+                                Name pnt = ((AdditionalEventInfo) e).getPrimaryNodeTypeName();
+                                if (pnt != null) {
+                                    writeObsElement(th, E_EVENTPRIMARNODETYPE, pnt.toString());
+                                }
+
+                                Set<Name> mixins = ((AdditionalEventInfo) e).getMixinTypeNames();
+                                if (mixins != null) {
+                                    for (Name mixin : mixins) {
+                                        writeObsElement(th, E_EVENTMIXINNODETYPE, mixin.toString());
+                                    }
+                                }
+
+                            } catch (UnsupportedRepositoryOperationException ex) {
+                                // optional
+                            }
+                        }
+
+                        // identifier
+                        if (e.getIdentifier() != null) {
+                            writeObsElement(th, E_EVENTIDENTIFIER, e.getIdentifier());
+                        }
+
+                        // info
+                        if (!e.getInfo().isEmpty()) {
+                            th.startElement(EVNS, E_EVENTINFO, E_EVENTINFO, NOATTRS);
+                            Map<?, ?> m = e.getInfo();
+                            for (Map.Entry<?, ?> entry : m.entrySet()) {
+                                String key = entry.getKey().toString();
+                                Object value = entry.getValue();
+                                String t = value != null ? value.toString() : null;
+                                writeElement(th, null, key, NOATTRS, t);
+                            }
+                            th.endElement(EVNS, E_EVENTINFO, E_EVENTINFO);
+                        }
+
+                        th.endElement(EVNS, E_EVENT, E_EVENT);
+
+                        lastTimeStamp = e.getDate();
+                        lastDateString = entryupd;
+                    }
+
+                    th.endElement(ATOMNS, CONTENT, CONTENT);
+                    th.endElement(ATOMNS, ENTRY, ENTRY);
+                }
+
+                th.endElement(ATOMNS, FEED, FEED);
+
+                th.endDocument();
+
+                os.flush();
+            }
+        } catch (Exception ex) {
+            throw new IOException("error generating feed: " + ex.getMessage());
+        }
+    }
+
+    @Override
+    public DavResource getCollection() {
+        return null;
+    }
+
+    @Override
+    public void addMember(DavResource resource, InputContext inputContext) throws DavException {
+        throw new DavException(DavServletResponse.SC_FORBIDDEN);
+    }
+
+    @Override
+    public DavResourceIterator getMembers() {
+        return DavResourceIteratorImpl.EMPTY;
+    }
+
+    @Override
+    public void removeMember(DavResource member) throws DavException {
+        throw new DavException(DavServletResponse.SC_FORBIDDEN);
+    }
+
+    @Override
+    protected void initLockSupport() {
+        // lock not allowed
+    }
+
+    @Override
+    protected String getWorkspaceHref() {
+        return getHref();
+    }
+
+    private void writeElement(TransformerHandler th, String ns, String name, Attributes attrs, String textContent)
+            throws SAXException {
+        th.startElement(ns, name, name, attrs);
+        if (textContent != null) {
+            th.characters(textContent.toCharArray(), 0, textContent.length());
+        }
+        th.endElement(ns, name, name);
+    }
+
+    private void writeAtomElement(TransformerHandler th, String name, Attributes attrs, String textContent)
+            throws SAXException {
+        writeElement(th, ATOMNS, name, attrs, textContent);
+    }
+
+    private void writeAtomElement(TransformerHandler th, String name, String textContent) throws SAXException {
+        writeAtomElement(th, name, NOATTRS, textContent);
+    }
+
+    private void writeObsElement(TransformerHandler th, String name, String textContent) throws SAXException {
+        writeElement(th, EVNS, name, NOATTRS, textContent);
+    }
+
+    private String getFullUri(HttpServletRequest req) {
+
+        String scheme = req.getScheme();
+        int port = req.getServerPort();
+        boolean isDefaultPort = (scheme.equals("http") && port == 80) || (scheme.equals("http") && port == 443);
+        String query = request.getQueryString() != null ? "?" + request.getQueryString() : "";
+
+        return String.format("%s://%s%s%s%s%s", scheme, req.getServerName(), isDefaultPort ? ":" : "",
+                isDefaultPort ? Integer.toString(port) : "", req.getRequestURI(), query);
+    }
+}
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/ItemResourceConstants.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/ItemResourceConstants.java
index 644a174..eda1c97 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/ItemResourceConstants.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/ItemResourceConstants.java
@@ -16,6 +16,8 @@
  */
 package org.apache.jackrabbit.webdav.jcr;
 
+import javax.jcr.Session;
+
 import org.apache.jackrabbit.commons.webdav.JcrRemotingConstants;
 import org.apache.jackrabbit.webdav.DavResource;
 import org.apache.jackrabbit.webdav.lock.Scope;
@@ -93,18 +95,31 @@ public interface ItemResourceConstants extends JcrRemotingConstants {
     //-----------------------------------------< JSR170 specific privileges >---
     /**
      * Privilege representing the JSR170 'read' action.
+     * <p><strong>Note:</strong> the name of this constant is somewhat misleading
+     * as it corresponds to {@link javax.jcr.Session#ACTION_READ} and not
+     * to {@link javax.jcr.security.Privilege#JCR_READ}.</p>
      */
-    public static final Privilege PRIVILEGE_JCR_READ = Privilege.getPrivilege("read", NAMESPACE);
+    public static final Privilege PRIVILEGE_JCR_READ = Privilege.getPrivilege(Session.ACTION_READ, NAMESPACE);
     /**
      * Privilege representing the JSR170 'add_node' action.
+     * <p><strong>Note:</strong> the name of this constant is somewhat misleading
+     * as it corresponds to {@link javax.jcr.Session#ACTION_ADD_NODE} and not
+     * to {@link javax.jcr.security.Privilege#JCR_ADD_CHILD_NODES}.</p>
      */
-    public static final Privilege PRIVILEGE_JCR_ADD_NODE = Privilege.getPrivilege("add_node", NAMESPACE);
+    public static final Privilege PRIVILEGE_JCR_ADD_NODE = Privilege.getPrivilege(Session.ACTION_ADD_NODE, NAMESPACE);
     /**
      * Privilege representing the JSR170 'set_property' action.
+     * <p><strong>Note:</strong> the name of this constant is somewhat misleading
+     * as it corresponds to {@link javax.jcr.Session#ACTION_SET_PROPERTY} and not
+     * to {@link javax.jcr.security.Privilege#JCR_MODIFY_PROPERTIES}.</p>
      */
-    public static final Privilege PRIVILEGE_JCR_SET_PROPERTY = Privilege.getPrivilege("set_property", NAMESPACE);
+    public static final Privilege PRIVILEGE_JCR_SET_PROPERTY = Privilege.getPrivilege(Session.ACTION_SET_PROPERTY, NAMESPACE);
     /**
      * Privilege representing the JSR170 'remove' action.
+     * <p><strong>Note:</strong> the name of this constant is somewhat misleading
+     * as it corresponds to {@link javax.jcr.Session#ACTION_REMOVE} and not
+     * to {@link javax.jcr.security.Privilege#JCR_REMOVE_NODE} or
+     * {@link javax.jcr.security.Privilege#JCR_REMOVE_CHILD_NODES}.</p>
      */
-    public static final Privilege PRIVILEGE_JCR_REMOVE = Privilege.getPrivilege("remove", NAMESPACE);
+    public static final Privilege PRIVILEGE_JCR_REMOVE = Privilege.getPrivilege(Session.ACTION_REMOVE, NAMESPACE);
 }
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/JcrDavSession.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/JcrDavSession.java
index 09b123e..d4fe9c2 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/JcrDavSession.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/JcrDavSession.java
@@ -19,10 +19,11 @@ package org.apache.jackrabbit.webdav.jcr;
 import org.apache.jackrabbit.webdav.DavException;
 import org.apache.jackrabbit.webdav.DavServletResponse;
 import org.apache.jackrabbit.webdav.DavSession;
-import org.apache.jackrabbit.webdav.DavConstants;
+import org.apache.jackrabbit.webdav.jcr.lock.LockTokenMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import java.util.HashSet;
 
@@ -96,8 +97,16 @@ public abstract class JcrDavSession implements DavSession {
      * @param token
      * @see DavSession#addLockToken(String)
      */
+    @Override
     public void addLockToken(String token) {
-        session.addLockToken(getJCRLockToken(token));
+        if (!LockTokenMapper.isForSessionScopedLock(token)) {
+            try {
+                session.getWorkspace().getLockManager().addLockToken(LockTokenMapper.getJcrLockToken(token));
+            }
+            catch (RepositoryException ex) {
+                log.debug("trying to add lock token " + token + " to session", ex);
+            }
+        }
         lockTokens.add(token);
     }
 
@@ -106,6 +115,7 @@ public abstract class JcrDavSession implements DavSession {
      * @return
      * @see DavSession#getLockTokens()
      */
+    @Override
     public String[] getLockTokens() {
         return lockTokens.toArray(new String[lockTokens.size()]);
     }
@@ -115,17 +125,16 @@ public abstract class JcrDavSession implements DavSession {
      * @param token
      * @see DavSession#removeLockToken(String)
      */
+    @Override
     public void removeLockToken(String token) {
-        session.removeLockToken(getJCRLockToken(token));
-        lockTokens.remove(token);
-    }
-
-    //------------------------------------------------------------< private >---
-    private static String getJCRLockToken(String token) {
-        if (token.startsWith(DavConstants.OPAQUE_LOCK_TOKEN_PREFIX)) {
-            return token.substring(DavConstants.OPAQUE_LOCK_TOKEN_PREFIX.length());
-        } else {
-            return token;
+        if (!LockTokenMapper.isForSessionScopedLock(token)) {
+            try {
+                session.getWorkspace().getLockManager().removeLockToken(LockTokenMapper.getJcrLockToken(token));
+            }
+            catch (RepositoryException ex) {
+                log.debug("trying to remove lock token " + token + " to session", ex);
+            }
         }
+        lockTokens.remove(token);
     }
 }
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/RootCollection.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/RootCollection.java
index 14d978b..a86a369 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/RootCollection.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/RootCollection.java
@@ -16,6 +16,17 @@
  */
 package org.apache.jackrabbit.webdav.jcr;
 
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Workspace;
+
 import org.apache.jackrabbit.util.Text;
 import org.apache.jackrabbit.webdav.DavException;
 import org.apache.jackrabbit.webdav.DavResource;
@@ -24,24 +35,18 @@ import org.apache.jackrabbit.webdav.DavResourceIterator;
 import org.apache.jackrabbit.webdav.DavResourceIteratorImpl;
 import org.apache.jackrabbit.webdav.DavResourceLocator;
 import org.apache.jackrabbit.webdav.DavServletResponse;
-import org.apache.jackrabbit.webdav.search.SearchResource;
 import org.apache.jackrabbit.webdav.io.InputContext;
 import org.apache.jackrabbit.webdav.io.OutputContext;
+import org.apache.jackrabbit.webdav.jcr.security.JcrSupportedPrivilegesProperty;
+import org.apache.jackrabbit.webdav.jcr.security.SecurityUtils;
+import org.apache.jackrabbit.webdav.property.DavProperty;
+import org.apache.jackrabbit.webdav.property.DavPropertyName;
+import org.apache.jackrabbit.webdav.search.SearchResource;
+import org.apache.jackrabbit.webdav.security.SecurityConstants;
 import org.apache.jackrabbit.webdav.version.DeltaVResource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Repository;
-import javax.jcr.Workspace;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Date;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.OutputStreamWriter;
-
 /**
  * <code>RootCollection</code> represent the WebDAV root resource that does not
  * represent any repository item. A call to getMembers() returns a
@@ -76,6 +81,7 @@ public class RootCollection extends AbstractResource {
      * @return string listing the METHODS allowed
      * @see org.apache.jackrabbit.webdav.DavResource#getSupportedMethods()
      */
+    @Override
     public String getSupportedMethods() {
         StringBuilder sb = new StringBuilder(DavResource.METHODS);
         sb.append(", ");
@@ -91,16 +97,33 @@ public class RootCollection extends AbstractResource {
      * @return true
      * @see org.apache.jackrabbit.webdav.DavResource#exists()
      */
+    @Override
     public boolean exists() {
         return true;
     }
 
+    @Override
+    public DavProperty<?> getProperty(DavPropertyName name) {
+        DavProperty prop = super.getProperty(name);
+        if (prop == null) {
+            try {
+                if (SecurityConstants.SUPPORTED_PRIVILEGE_SET.equals(name)) {
+                    prop = new JcrSupportedPrivilegesProperty(getRepositorySession()).asDavProperty();
+                }
+            } catch (RepositoryException e) {
+                log.error("Failed to build SupportedPrivilegeSet property: " + e.getMessage());
+            }
+        }
+        return prop;
+    }
+
     /**
      * Returns true
      *
      * @return true
      * @see org.apache.jackrabbit.webdav.DavResource#isCollection()
      */
+    @Override
     public boolean isCollection() {
         return true;
     }
@@ -111,6 +134,7 @@ public class RootCollection extends AbstractResource {
      * @return empty string
      * @see org.apache.jackrabbit.webdav.DavResource#getDisplayName()
      */
+    @Override
     public String getDisplayName() {
         return "";
     }
@@ -121,6 +145,7 @@ public class RootCollection extends AbstractResource {
      * @return
      * @see org.apache.jackrabbit.webdav.DavResource#getModificationTime()
      */
+    @Override
     public long getModificationTime() {
         return new Date().getTime();
     }
@@ -132,6 +157,7 @@ public class RootCollection extends AbstractResource {
      * @throws IOException
      * @see DavResource#spool(org.apache.jackrabbit.webdav.io.OutputContext)
      */
+    @Override
     public void spool(OutputContext outputContext) throws IOException {
         if (outputContext.hasStream()) {
             Session session = getRepositorySession();
@@ -180,6 +206,7 @@ public class RootCollection extends AbstractResource {
      * of any resource.
      * @see org.apache.jackrabbit.webdav.DavResource#getCollection()
      */
+    @Override
     public DavResource getCollection() {
         return null;
     }
@@ -188,6 +215,7 @@ public class RootCollection extends AbstractResource {
      * Throws exception: 403 Forbidden.
      * @see DavResource#addMember(DavResource, InputContext)
      */
+    @Override
     public void addMember(DavResource resource, InputContext inputContext) throws DavException {
         throw new DavException(DavServletResponse.SC_FORBIDDEN);
     }
@@ -199,6 +227,7 @@ public class RootCollection extends AbstractResource {
      * @return members of this collection
      * @see org.apache.jackrabbit.webdav.DavResource#getMembers()
      */
+    @Override
     public DavResourceIterator getMembers() {
         List<DavResource> memberList = new ArrayList();
         try {
@@ -223,6 +252,7 @@ public class RootCollection extends AbstractResource {
      *
      * @see DavResource#removeMember(org.apache.jackrabbit.webdav.DavResource)
      */
+    @Override
     public void removeMember(DavResource member) throws DavException {
         Workspace wsp = getRepositorySession().getWorkspace();
         String name = Text.getName(member.getResourcePath());
@@ -269,4 +299,12 @@ public class RootCollection extends AbstractResource {
     protected String getWorkspaceHref() {
         return null;
     }
+
+    @Override
+    protected void initPropertyNames() {
+        super.initPropertyNames();
+        if (SecurityUtils.supportsAccessControl(getRepositorySession())) {
+            names.add(SecurityConstants.SUPPORTED_PRIVILEGE_SET);
+        }
+    }
 }
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/VersionControlledItemCollection.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/VersionControlledItemCollection.java
index 2321611..a57909f 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/VersionControlledItemCollection.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/VersionControlledItemCollection.java
@@ -281,6 +281,7 @@ public class VersionControlledItemCollection extends DefaultItemCollection
      * exist yet or if an error occurs while making the underlying node versionable.
      * @see org.apache.jackrabbit.webdav.version.VersionableResource#addVersionControl()
      */
+    @Override
     public void addVersionControl() throws DavException {
         if (!exists()) {
             throw new DavException(DavServletResponse.SC_NOT_FOUND);
@@ -302,6 +303,7 @@ public class VersionControlledItemCollection extends DefaultItemCollection
      * @throws org.apache.jackrabbit.webdav.DavException
      * @see org.apache.jackrabbit.webdav.version.VersionControlledResource#checkin()
      */
+    @Override
     public String checkin() throws DavException {
         if (!exists()) {
             throw new DavException(DavServletResponse.SC_NOT_FOUND);
@@ -325,6 +327,7 @@ public class VersionControlledItemCollection extends DefaultItemCollection
      * @throws org.apache.jackrabbit.webdav.DavException
      * @see org.apache.jackrabbit.webdav.version.VersionControlledResource#checkout()
      */
+    @Override
     public void checkout() throws DavException {
         if (!exists()) {
             throw new DavException(DavServletResponse.SC_NOT_FOUND);
@@ -347,6 +350,7 @@ public class VersionControlledItemCollection extends DefaultItemCollection
      * @throws org.apache.jackrabbit.webdav.DavException
      * @see org.apache.jackrabbit.webdav.version.VersionControlledResource#uncheckout()
      */
+    @Override
     public void uncheckout() throws DavException {
         throw new DavException(DavServletResponse.SC_NOT_IMPLEMENTED);
     }
@@ -370,6 +374,7 @@ public class VersionControlledItemCollection extends DefaultItemCollection
      * @see org.apache.jackrabbit.webdav.version.VersionControlledResource#update(org.apache.jackrabbit.webdav.version.UpdateInfo)
      */
     //TODO: with jcr the node must not be versionable in order to perform Node.update.
+    @Override
     public MultiStatus update(UpdateInfo updateInfo) throws DavException {
         if (updateInfo == null) {
             throw new DavException(DavServletResponse.SC_BAD_REQUEST, "Valid update request body required.");
@@ -418,7 +423,8 @@ public class VersionControlledItemCollection extends DefaultItemCollection
                 node.restoreByLabel(labels[0], removeExisting);
 
             } else if (updateInfo.getWorkspaceHref() != null) {
-                String workspaceName = getLocatorFromHref(updateInfo.getWorkspaceHref()).getWorkspaceName();
+                String href = obtainAbsolutePathFromUri(updateInfo.getWorkspaceHref());
+                String workspaceName = getLocatorFromHref(href).getWorkspaceName();
                 node.update(workspaceName);
             } else {
                 throw new DavException(DavServletResponse.SC_BAD_REQUEST, "Invalid update request body.");
@@ -446,6 +452,7 @@ public class VersionControlledItemCollection extends DefaultItemCollection
      * @see Node#merge(String, boolean)
      */
     //TODO: with jcr the node must not be versionable in order to perform Node.merge
+    @Override
     public MultiStatus merge(MergeInfo mergeInfo) throws DavException {
         if (mergeInfo == null) {
             throw new DavException(DavServletResponse.SC_BAD_REQUEST);
@@ -492,6 +499,7 @@ public class VersionControlledItemCollection extends DefaultItemCollection
      * @see VersionHistory#addVersionLabel(String, String, boolean)
      * @see VersionHistory#removeVersionLabel(String)
      */
+    @Override
     public void label(LabelInfo labelInfo) throws DavException {
         if (labelInfo == null) {
             throw new DavException(DavServletResponse.SC_BAD_REQUEST, "Valid label request body required.");
@@ -524,6 +532,7 @@ public class VersionControlledItemCollection extends DefaultItemCollection
      * @see org.apache.jackrabbit.webdav.version.VersionControlledResource#getVersionHistory()
      * @see javax.jcr.Node#getVersionHistory()
      */
+    @Override
     public VersionHistoryResource getVersionHistory() throws DavException {
         if (!exists()) {
             throw new DavException(DavServletResponse.SC_NOT_FOUND);
@@ -674,4 +683,20 @@ public class VersionControlledItemCollection extends DefaultItemCollection
     private VersionManager getVersionManager() throws RepositoryException {
         return getRepositorySession().getWorkspace().getVersionManager();
     }
+
+    private static String obtainAbsolutePathFromUri(String uri) {
+        try {
+            java.net.URI u = new java.net.URI(uri);
+            StringBuilder sb = new StringBuilder();
+            sb.append(u.getRawPath());
+            if (u.getRawQuery() != null) {
+                sb.append("?").append(u.getRawQuery());
+            }
+            return sb.toString();
+        }
+        catch (java.net.URISyntaxException ex) {
+            log.warn("parsing " + uri, ex);
+            return uri;
+        }
+    }
 }
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/WorkspaceResourceImpl.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/WorkspaceResourceImpl.java
index 6b42b18..d45b250 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/WorkspaceResourceImpl.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/WorkspaceResourceImpl.java
@@ -36,12 +36,16 @@ import org.apache.jackrabbit.webdav.io.InputContext;
 import org.apache.jackrabbit.webdav.io.OutputContext;
 import org.apache.jackrabbit.webdav.jcr.property.JcrDavPropertyNameSet;
 import org.apache.jackrabbit.webdav.jcr.property.NamespacesProperty;
+import org.apache.jackrabbit.webdav.jcr.security.JcrUserPrivilegesProperty;
+import org.apache.jackrabbit.webdav.jcr.security.JcrSupportedPrivilegesProperty;
+import org.apache.jackrabbit.webdav.jcr.security.SecurityUtils;
 import org.apache.jackrabbit.webdav.jcr.version.report.JcrPrivilegeReport;
 import org.apache.jackrabbit.webdav.property.DavProperty;
 import org.apache.jackrabbit.webdav.property.DavPropertyName;
 import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
 import org.apache.jackrabbit.webdav.property.PropEntry;
 import org.apache.jackrabbit.webdav.search.SearchResource;
+import org.apache.jackrabbit.webdav.security.SecurityConstants;
 import org.apache.jackrabbit.webdav.version.DeltaVResource;
 import org.apache.jackrabbit.webdav.version.LabelInfo;
 import org.apache.jackrabbit.webdav.version.MergeInfo;
@@ -104,35 +108,43 @@ public class WorkspaceResourceImpl extends AbstractResource
     @Override
     public DavProperty<?> getProperty(DavPropertyName name) {
         DavProperty prop = super.getProperty(name);
-        if (prop == null && ItemResourceConstants.JCR_NODETYPES_CND.equals(name)) {
-            StringWriter writer = new StringWriter();
+        if (prop == null) {
+            StringWriter writer = null;
             try {
-                Session s = getRepositorySession();
-
-                CompactNodeTypeDefWriter cndWriter = new CompactNodeTypeDefWriter(writer, s, true);
-                NodeTypeIterator ntIterator = s.getWorkspace().getNodeTypeManager().getAllNodeTypes();
-                while (ntIterator.hasNext()) {
-                    cndWriter.write(ntIterator.nextNodeType());
+                if (ItemResourceConstants.JCR_NODETYPES_CND.equals(name)) {
+                    writer = new StringWriter();
+                    Session s = getRepositorySession();
+
+                    CompactNodeTypeDefWriter cndWriter = new CompactNodeTypeDefWriter(writer, s, true);
+                    NodeTypeIterator ntIterator = s.getWorkspace().getNodeTypeManager().getAllNodeTypes();
+                    while (ntIterator.hasNext()) {
+                        cndWriter.write(ntIterator.nextNodeType());
+                    }
+                    cndWriter.close();
+                    /*
+                    NOTE: avoid having JCR_NODETYPES_CND exposed upon allprop
+                          PROPFIND request since it needs to be calculated.
+                          nevertheless, this property can be altered using
+                          PROPPATCH, which is not consistent with the specification
+                    */
+                    prop = new DefaultDavProperty<String>(ItemResourceConstants.JCR_NODETYPES_CND, writer.toString(), true);
+
+                } else if (SecurityConstants.SUPPORTED_PRIVILEGE_SET.equals(name)) {
+                    prop = new JcrSupportedPrivilegesProperty(getRepositorySession(), null).asDavProperty();
+                } else if (SecurityConstants.CURRENT_USER_PRIVILEGE_SET.equals(name)) {
+                    prop = new JcrUserPrivilegesProperty(getRepositorySession(), null).asDavProperty();
                 }
-                cndWriter.close();
-                /*
-                NOTE: avoid having JCR_NODETYPES_CND exposed upon allprop
-                      PROPFIND request since it needs to be calculated.
-                      nevertheless, this property can be altered using
-                      PROPPATCH, which is not consistent with the specification
-                 */
-                return new DefaultDavProperty<String>(ItemResourceConstants.JCR_NODETYPES_CND, writer.toString(), true);
             } catch (RepositoryException e) {
                 log.error("Failed to access NodeTypeManager: " + e.getMessage());
-                return null;
             } catch (IOException e) {
                 log.error("Failed to write compact node definition: " + e.getMessage());
-                return null;
             } finally {
-                try {
-                    writer.close();
-                } catch (IOException e) {
-                    log.error(e.getMessage());
+                if (writer != null) {
+                    try {
+                        writer.close();
+                    } catch (IOException e) {
+                        log.error(e.getMessage());
+                    }
                 }
             }
         }
@@ -144,6 +156,7 @@ public class WorkspaceResourceImpl extends AbstractResource
 
     //--------------------------------------------------------< DavResource >---
 
+    @Override
     public String getSupportedMethods() {
         StringBuilder sb = new StringBuilder(DavResource.METHODS);
         sb.append(", ");
@@ -161,6 +174,7 @@ public class WorkspaceResourceImpl extends AbstractResource
      * present in the list of available workspace names such as exposed by
      * the editing JCR session.
      */
+    @Override
     public boolean exists() {
         try {
             List<String> available = Arrays.asList(getRepositorySession().getWorkspace().getAccessibleWorkspaceNames());
@@ -174,6 +188,7 @@ public class WorkspaceResourceImpl extends AbstractResource
     /**
      * @return true
      */
+    @Override
     public boolean isCollection() {
         return true;
     }
@@ -185,6 +200,7 @@ public class WorkspaceResourceImpl extends AbstractResource
      * @see org.apache.jackrabbit.webdav.DavResource#getDisplayName()
      * @see javax.jcr.Workspace#getName()
      */
+    @Override
     public String getDisplayName() {
         return getLocator().getWorkspaceName();
     }
@@ -194,6 +210,7 @@ public class WorkspaceResourceImpl extends AbstractResource
      *
      * @return
      */
+    @Override
     public long getModificationTime() {
         return new Date().getTime();
     }
@@ -202,6 +219,7 @@ public class WorkspaceResourceImpl extends AbstractResource
      * @param outputContext
      * @throws IOException
      */
+    @Override
     public void spool(OutputContext outputContext) throws IOException {
 
         outputContext.setProperty("Link", "<?" + EventJournalResourceImpl.RELURIFROMWORKSPACE
@@ -255,6 +273,7 @@ public class WorkspaceResourceImpl extends AbstractResource
      *
      * @see org.apache.jackrabbit.webdav.DavResource#getCollection()
      */
+    @Override
     public DavResource getCollection() {
         DavResource collection = null;
         // create location with 'null' values for workspace-path and resource-path
@@ -274,6 +293,7 @@ public class WorkspaceResourceImpl extends AbstractResource
      * @param inputContext
      * @throws DavException
      */
+    @Override
     public void addMember(DavResource resource, InputContext inputContext) throws DavException {
         log.error("Cannot add a new member to the workspace resource.");
         throw new DavException(DavServletResponse.SC_FORBIDDEN);
@@ -284,6 +304,7 @@ public class WorkspaceResourceImpl extends AbstractResource
      *
      * @return
      */
+    @Override
     public DavResourceIterator getMembers() {
         try {
             DavResourceLocator loc = getLocatorFromItem(getRepositorySession().getRootNode());
@@ -304,6 +325,7 @@ public class WorkspaceResourceImpl extends AbstractResource
      * @param member
      * @throws DavException
      */
+    @Override
     public void removeMember(DavResource member) throws DavException {
         log.error("Cannot add a remove the root node.");
         throw new DavException(DavServletResponse.SC_FORBIDDEN);
@@ -311,7 +333,7 @@ public class WorkspaceResourceImpl extends AbstractResource
 
     /**
      * Allows to alter the registered namespaces ({@link ItemResourceConstants#JCR_NAMESPACES})
-     * or register node types ({@link ItemResourceConstants#JCR_NODETYPES_CND)
+     * or register node types {@link ItemResourceConstants#JCR_NODETYPES_CND}
      * where the passed value is a cnd string containing the definition
      * and forwards any other property to the super class.<p/>
      * Note that again no property status is set. Any failure while setting
@@ -441,6 +463,7 @@ public class WorkspaceResourceImpl extends AbstractResource
      * @throws DavException (403) since workspace is not versionable. implementing
      * <code>VersionControlledResource</code> only for 'update'.
      */
+    @Override
     public void addVersionControl() throws DavException {
         throw new DavException(DavServletResponse.SC_FORBIDDEN);
     }
@@ -450,6 +473,7 @@ public class WorkspaceResourceImpl extends AbstractResource
      * @throws DavException (403) since workspace is not versionable. implementing
      * <code>VersionControlledResource</code> only for 'update'.
      */
+    @Override
     public String checkin() throws DavException {
         throw new DavException(DavServletResponse.SC_FORBIDDEN);
     }
@@ -458,6 +482,7 @@ public class WorkspaceResourceImpl extends AbstractResource
      * @throws DavException (403) since workspace is not versionable. implementing
      * <code>VersionControlledResource</code> only for 'update'.
      */
+    @Override
     public void checkout() throws DavException {
         throw new DavException(DavServletResponse.SC_FORBIDDEN);
     }
@@ -466,6 +491,7 @@ public class WorkspaceResourceImpl extends AbstractResource
      * @throws DavException (403) since workspace is not versionable. implementing
      * <code>VersionControlledResource</code> only for 'update'.
      */
+    @Override
     public void uncheckout() throws DavException {
         throw new DavException(DavServletResponse.SC_FORBIDDEN);
     }
@@ -484,6 +510,7 @@ public class WorkspaceResourceImpl extends AbstractResource
      * @throws org.apache.jackrabbit.webdav.DavException
      * @see org.apache.jackrabbit.webdav.version.VersionControlledResource#update(org.apache.jackrabbit.webdav.version.UpdateInfo)
      */
+    @Override
     public MultiStatus update(UpdateInfo updateInfo) throws DavException {
         if (updateInfo == null) {
             throw new DavException(DavServletResponse.SC_BAD_REQUEST, "Valid update request body required.");
@@ -533,6 +560,7 @@ public class WorkspaceResourceImpl extends AbstractResource
      * @throws DavException (403) since workspace is not versionable. implementing
      * <code>VersionControlledResource</code> only for 'update'.
      */
+    @Override
     public MultiStatus merge(MergeInfo mergeInfo) throws DavException {
         throw new DavException(DavServletResponse.SC_FORBIDDEN);
     }
@@ -541,6 +569,7 @@ public class WorkspaceResourceImpl extends AbstractResource
      * @throws DavException (403) since workspace is not versionable. implementing
      * <code>VersionControlledResource</code> only for 'update'.
      */
+    @Override
     public void label(LabelInfo labelInfo) throws DavException {
         throw new DavException(DavServletResponse.SC_FORBIDDEN);
     }
@@ -549,6 +578,7 @@ public class WorkspaceResourceImpl extends AbstractResource
      * @throws DavException (403) since workspace is not versionable. implementing
      * <code>VersionControlledResource</code> only for 'update'.
      */
+    @Override
     public VersionHistoryResource getVersionHistory() throws DavException {
         throw new DavException(DavServletResponse.SC_FORBIDDEN);
     }
@@ -574,6 +604,10 @@ public class WorkspaceResourceImpl extends AbstractResource
     protected void initPropertyNames() {
         super.initPropertyNames();
         names.addAll(JcrDavPropertyNameSet.WORKSPACE_SET);
+        if (SecurityUtils.supportsAccessControl(getRepositorySession())) {
+            names.add(SecurityConstants.SUPPORTED_PRIVILEGE_SET);
+            names.add(SecurityConstants.CURRENT_USER_PRIVILEGE_SET);
+        }
     }
 
     @Override
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/lock/JcrActiveLock.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/lock/JcrActiveLock.java
index d7bb764..f7ebd9a 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/lock/JcrActiveLock.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/lock/JcrActiveLock.java
@@ -84,7 +84,7 @@ public class JcrActiveLock extends AbstractActiveLock implements ActiveLock, Dav
      * UUID [Extension] ; The UUID production is the string representation of a
      * UUID, as defined in [ISO-11578]. Note that white space (LWS) is not allowed
      * between elements of this production.</cite>").
-     * <p/>
+     * <p>
      * In case of session-scoped JCR 2.0 locks, the token is never exposed even
      * if the current session is lock holder. In order to cope with DAV specific
      * requirements and the fulfill the requirement stated above, the node's
@@ -93,20 +93,14 @@ public class JcrActiveLock extends AbstractActiveLock implements ActiveLock, Dav
      * @see ActiveLock#getToken()
      */
     public String getToken() {
-        String token = lock.getLockToken();
-        if (token == null && lock.isSessionScoped()
-                && lock.isLockOwningSession()) {
-            // special handling for session scoped locks that are owned by the
-            // current session but never expose their token with jsr 283.
-            try {
-                token = lock.getNode().getIdentifier();
-            } catch (RepositoryException e) {
-                // should never get here
-                log.warn("Unexpected error while retrieving node identifier for building a DAV specific lock token.",e.getMessage());
-            }
+        try {
+            return LockTokenMapper.getDavLocktoken(lock);
+        } catch (RepositoryException e) {
+            // should never get here
+            log.warn("Unexpected error while retrieving node identifier for building a DAV specific lock token. {}",
+                    e.getMessage());
+            return null;
         }
-        // default behaviour: just return the token exposed by the lock.
-        return token;
     }
 
     /**
@@ -124,14 +118,33 @@ public class JcrActiveLock extends AbstractActiveLock implements ActiveLock, Dav
     }
 
     /**
-     * Since jcr locks do not reveal the time left until they expire, {@link #INFINITE_TIMEOUT}
-     * is returned. A missing timeout causes problems with Microsoft clients.
+     * Calculates the milliseconds of the timeout from
+     * {@link javax.jcr.lock.Lock#getSecondsRemaining()}. If the timeout of
+     * jcr lock is undefined or infinite {@link #INFINITE_TIMEOUT} is
+     * returned.
      *
-     * @return Always returns {@link #INFINITE_TIMEOUT}
      * @see ActiveLock#getTimeout()
      */
     public long getTimeout() {
-        return INFINITE_TIMEOUT;
+        try {
+            long to = lock.getSecondsRemaining();
+            long reportAs;
+
+            if (to == Long.MAX_VALUE) {
+                reportAs = INFINITE_TIMEOUT;
+            }
+            else if (to / 1000 <= Long.MAX_VALUE / 1000) {
+                // expressible as long?
+                reportAs = to * 1000;
+            }
+            else {
+                reportAs = INFINITE_TIMEOUT;
+            }
+
+            return reportAs;
+        } catch (RepositoryException e) {
+            return INFINITE_TIMEOUT;
+        }
     }
 
     /**
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/lock/LockTokenMapper.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/lock/LockTokenMapper.java
new file mode 100644
index 0000000..2daba0a
--- /dev/null
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/lock/LockTokenMapper.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.jcr.lock;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.lock.Lock;
+
+import org.apache.jackrabbit.util.Text;
+
+/**
+ * Maps between WebDAV lock tokens and JCR lock tokens.
+ * <p>
+ * The following notations are used:
+ * 
+ * <pre>
+ * opaquelocktoken:SESSIONSCOPED:<em>NODEIDENTIFIER</em>
+ * opaquelocktoken:OPENSCOPED:<em>JCRLOCKTOKEN</em>
+ * </pre>
+ * 
+ * The first format is used if the JCR lock does not reveal a lock token, such
+ * as when it is a session-scoped lock (where SESSIONSCOPED is a constant UUID
+ * defined below, and NODEIDENTIFIER is the suitably escaped JCR Node
+ * identifier).
+ * <p>
+ * The second format is used for open-scoped locks (where OPENSCOPED is another
+ * constant UUID defined below, and JCRLOCKTOKEN is the suitably escaped JCR
+ * lock token).
+ */
+public class LockTokenMapper {
+
+    private static final String OL = "opaquelocktoken:";
+
+    private static final String SESSIONSCOPED = "4403ef44-4124-11e1-b965-00059a3c7a00";
+    private static final String OPENSCOPED = "dccce564-412e-11e1-b969-00059a3c7a00";
+
+    private static final String SESSPREFIX = OL + SESSIONSCOPED + ":";
+    private static final String OPENPREFIX = OL + OPENSCOPED + ":";
+
+    public static String getDavLocktoken(Lock lock) throws RepositoryException {
+        String jcrLockToken = lock.getLockToken();
+
+        if (jcrLockToken == null) {
+            return SESSPREFIX + Text.escape(lock.getNode().getIdentifier());
+        } else {
+            return OPENPREFIX + Text.escape(jcrLockToken);
+        }
+    }
+
+    public static String getJcrLockToken(String token) throws RepositoryException {
+        if (token.startsWith(OPENPREFIX)) {
+            return Text.unescape(token.substring(OPENPREFIX.length()));
+        } else {
+            throw new RepositoryException("not a token for an open-scoped JCR lock: " + token);
+        }
+    }
+
+    public static boolean isForSessionScopedLock(String token) {
+        return token.startsWith(SESSPREFIX);
+    }
+}
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/observation/SubscriptionImpl.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/observation/SubscriptionImpl.java
index 4f3f1f2..7fee0af 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/observation/SubscriptionImpl.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/observation/SubscriptionImpl.java
@@ -18,7 +18,6 @@ package org.apache.jackrabbit.webdav.jcr.observation;
 
 import org.apache.jackrabbit.commons.webdav.EventUtil;
 import org.apache.jackrabbit.commons.webdav.JcrRemotingConstants;
-import org.apache.jackrabbit.server.SessionProviderImpl;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.commons.AdditionalEventInfo;
 import org.apache.jackrabbit.spi.commons.SessionExtensions;
@@ -45,12 +44,9 @@ import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
-import com.sun.org.apache.xalan.internal.xsltc.dom.ExtendedSAX;
-
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.UnsupportedRepositoryOperationException;
-import javax.jcr.Value;
 import javax.jcr.observation.Event;
 import javax.jcr.observation.EventIterator;
 import javax.jcr.observation.EventListener;
@@ -466,18 +462,18 @@ public class SubscriptionImpl implements Subscription, ObservationConstants, Eve
                 if (!localFlagSet) {
                     // obtain remote session identifier
                     localFlagSet = true;
-                    String forSessionId = (String) session
-                            .getAttribute(SessionProviderImpl.ATTRIBUTE_SESSION_ID);
+                    String name = JcrRemotingConstants.RELATION_REMOTE_SESSION_ID;
+                    Object forSessionId = session.getAttribute(name);
                     // calculate "local" flags
                     if (forSessionId != null
                             && event instanceof AdditionalEventInfo) {
+                        AdditionalEventInfo aei = (AdditionalEventInfo) event;
                         try {
-                            String eventforSessionId = (String) ((AdditionalEventInfo) event)
-                                    .getSessionAttribute(SessionProviderImpl.ATTRIBUTE_SESSION_ID);
-                            boolean isLocal = forSessionId
-                                    .equals(eventforSessionId);
-                            DomUtil.setAttribute(bundle, XML_EVENT_LOCAL,
-                                    NAMESPACE, Boolean.toString(isLocal));
+                            boolean isLocal = forSessionId.equals(
+                                    aei.getSessionAttribute(name));
+                            DomUtil.setAttribute(
+                                    bundle, XML_EVENT_LOCAL, null,
+                                    Boolean.toString(isLocal));
                         } catch (UnsupportedRepositoryOperationException ex) {
                             // optional feature
                         }
@@ -524,19 +520,19 @@ public class SubscriptionImpl implements Subscription, ObservationConstants, Eve
                 try {
                     DomUtil.addChildElement(eventElem, XML_EVENTUSERDATA, NAMESPACE, event.getUserData());
                 } catch (RepositoryException e) {
-                    log.error("Internal error while retrieving event user data.", e.getMessage());
+                    log.error("Internal error while retrieving event user data. {}", e.getMessage());
                 }
                 // time stamp
                 try {
                     DomUtil.addChildElement(eventElem, XML_EVENTDATE, NAMESPACE, String.valueOf(event.getDate()));
                 } catch (RepositoryException e) {
-                    log.error("Internal error while retrieving event date.", e.getMessage());
+                    log.error("Internal error while retrieving event date. {}", e.getMessage());
                 }
                 // identifier
                 try {
                     DomUtil.addChildElement(eventElem, XML_EVENTIDENTIFIER, NAMESPACE, event.getIdentifier());
                 } catch (RepositoryException e) {
-                    log.error("Internal error while retrieving event identifier.", e.getMessage());
+                    log.error("Internal error while retrieving event identifier. {}", e.getMessage());
                 }
                 // info
                 Element info = DomUtil.addChildElement(eventElem, XML_EVENTINFO, NAMESPACE);
@@ -552,7 +548,7 @@ public class SubscriptionImpl implements Subscription, ObservationConstants, Eve
                         }
                     }
                 } catch (RepositoryException e) {
-                    log.error("Internal error while retrieving event info.", e.getMessage());
+                    log.error("Internal error while retrieving event info. {}", e.getMessage());
                 }
             }
             return bundle;
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/property/JcrDavPropertyNameSet.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/property/JcrDavPropertyNameSet.java
index ff26b23..480212b 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/property/JcrDavPropertyNameSet.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/property/JcrDavPropertyNameSet.java
@@ -20,7 +20,6 @@ import org.apache.jackrabbit.webdav.jcr.ItemResourceConstants;
 import org.apache.jackrabbit.webdav.observation.ObservationConstants;
 import org.apache.jackrabbit.webdav.property.DavPropertyName;
 import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
-import org.apache.jackrabbit.webdav.security.SecurityConstants;
 import org.apache.jackrabbit.webdav.version.DeltaVConstants;
 import org.apache.jackrabbit.webdav.version.VersionControlledResource;
 import org.apache.jackrabbit.webdav.version.VersionHistoryResource;
@@ -77,7 +76,6 @@ public final class JcrDavPropertyNameSet implements ItemResourceConstants {
         ITEM_BASE_SET.add(DavPropertyName.GETCONTENTTYPE);        
         ITEM_BASE_SET.add(DeltaVConstants.WORKSPACE);
         ITEM_BASE_SET.add(ObservationConstants.SUBSCRIPTIONDISCOVERY);
-        ITEM_BASE_SET.add(SecurityConstants.CURRENT_USER_PRIVILEGE_SET);
     }
 
     /**
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/search/SearchResourceImpl.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/search/SearchResourceImpl.java
index 2c01127..304a0ec 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/search/SearchResourceImpl.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/search/SearchResourceImpl.java
@@ -22,6 +22,7 @@ import org.apache.jackrabbit.webdav.DavResourceLocator;
 import org.apache.jackrabbit.webdav.DavServletResponse;
 import org.apache.jackrabbit.webdav.MultiStatus;
 import org.apache.jackrabbit.webdav.MultiStatusResponse;
+import org.apache.jackrabbit.webdav.jcr.ItemResourceConstants;
 import org.apache.jackrabbit.webdav.jcr.JcrDavException;
 import org.apache.jackrabbit.webdav.jcr.JcrDavSession;
 import org.apache.jackrabbit.webdav.search.QueryGrammerSet;
@@ -45,16 +46,10 @@ import javax.jcr.query.QueryManager;
 import javax.jcr.query.QueryResult;
 import javax.jcr.query.Row;
 import javax.jcr.query.RowIterator;
-import javax.jcr.query.qom.Source;
-import javax.jcr.query.qom.Join;
-import javax.jcr.query.qom.Selector;
-import javax.jcr.query.qom.QueryObjectModel;
 
 import java.util.Map;
-import java.util.Iterator;
 import java.util.List;
 import java.util.ArrayList;
-import java.util.Arrays;
 
 /**
  * <code>SearchResourceImpl</code>...
@@ -81,7 +76,10 @@ public class SearchResourceImpl implements SearchResource {
             QueryManager qMgr = getRepositorySession().getWorkspace().getQueryManager();
             String[] langs = qMgr.getSupportedQueryLanguages();
             for (String lang : langs) {
-                // todo: define proper namespace
+                // Note: Existing clients already assume that the
+                // query languages returned in the DASL header are
+                // not prefixed with any namespace, so we probably
+                // shouldn't use an explicit namespace here.
                 qgs.addQueryLanguage(lang, Namespace.EMPTY_NAMESPACE);
             }
         } catch (RepositoryException e) {
@@ -97,8 +95,22 @@ public class SearchResourceImpl implements SearchResource {
      */
     public MultiStatus search(SearchInfo sInfo) throws DavException {
         try {
-            return queryResultToMultiStatus(getQuery(sInfo));
+            QueryResult result = getQuery(sInfo).execute();
 
+            MultiStatus ms = new MultiStatus();
+
+            if (ItemResourceConstants.NAMESPACE.equals(
+                    sInfo.getLanguageNameSpace())) {
+                ms.setResponseDescription(
+                        "Columns: " + encode(result.getColumnNames())
+                        + "\nSelectors: " + encode(result.getSelectorNames()));
+            } else {
+                ms.setResponseDescription(encode(result.getColumnNames()));
+            }
+
+            queryResultToMultiStatus(result, ms);
+
+            return ms;
         } catch (RepositoryException e) {
             throw new JcrDavException(e);
         }
@@ -196,44 +208,34 @@ public class SearchResourceImpl implements SearchResource {
      * Webdav compatible form.
      * @throws RepositoryException if an error occurs.
      */
-    private MultiStatus queryResultToMultiStatus(Query query)
+    private void queryResultToMultiStatus(QueryResult result, MultiStatus ms)
             throws RepositoryException {
-        QueryResult qResult = query.execute();
-        MultiStatus ms = new MultiStatus();
-
         List<String> columnNames = new ArrayList<String>();
-        columnNames.addAll(Arrays.asList(qResult.getColumnNames()));
-        StringBuffer responseDescription = new StringBuffer();
-        String delim = "";
-        for (String columnName : columnNames) {
-            responseDescription.append(delim);
-            responseDescription.append(ISO9075.encode(columnName));
-            delim = " ";
-        }
-        ms.setResponseDescription(responseDescription.toString());
 
         ValueFactory vf = getRepositorySession().getValueFactory();
         List<RowValue> descr = new ArrayList<RowValue>();
-        for (Iterator<String> it = columnNames.iterator(); it.hasNext(); ) {
-            String columnName = it.next();
+        for (String columnName : result.getColumnNames()) {
             if (!isPathOrScore(columnName)) {
+                columnNames.add(columnName);
                 descr.add(new PlainValue(columnName, null, vf));
-            } else {
-                it.remove();
             }
         }
+
         // add path and score for each selector
-        List<String> sn = new ArrayList<String>();
-        collectSelectorNames(query, qResult, sn);
-        for (String selectorName : sn) {
+        String[] sns = result.getSelectorNames();
+        boolean join = sns.length > 1;
+        for (String selectorName : sns) {
             descr.add(new PathValue(JcrConstants.JCR_PATH, selectorName, vf));
             columnNames.add(JcrConstants.JCR_PATH);
             descr.add(new ScoreValue(JcrConstants.JCR_SCORE, selectorName, vf));
             columnNames.add(JcrConstants.JCR_SCORE);
         }
+
+        int n = 0;
+        String root = getHref("/");
         String[] selectorNames = createSelectorNames(descr);
         String[] colNames = columnNames.toArray(new String[columnNames.size()]);
-        RowIterator rowIter = qResult.getRows();
+        RowIterator rowIter = result.getRows();
         while (rowIter.hasNext()) {
             Row row = rowIter.nextRow();
             List<Value> values = new ArrayList<Value>();
@@ -241,27 +243,49 @@ public class SearchResourceImpl implements SearchResource {
                 values.add(rv.getValue(row));
             }
 
-            /*
-             * get the path for the first selector and build a webdav compliant
-             * resource path based on it.
-             * 
-             * Use Row#getPath(String) which works for both simple rows and join
-             * rows (in contrast to Row#getPath()).
-             * 
-             * see also https://issues.apache.org/jira/browse/JCR-3089
-             */
-            final String itemPath = row.getPath(sn.get(0));
             // create a new ms-response for this row of the result set
-            DavResourceLocator loc = locator.getFactory().createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(), itemPath, false);
-            String href = loc.getHref(true);
+            String href;
+            if (join) {
+                // We need a distinct href for each join result row to
+                // allow the MultiStatus response to keep them separate
+                href = root + "?" + n++;
+            } else {
+                href = getHref(row.getPath());
+            }
             MultiStatusResponse resp = new MultiStatusResponse(href, null);
+
             // build the s-r-property
             SearchResultProperty srp = new SearchResultProperty(colNames,
                     selectorNames, values.toArray(new Value[values.size()]));
             resp.add(srp);
             ms.addResponse(resp);
         }
-        return ms;
+    }
+
+    /**
+     * Returns the resource location of the given query result row.
+     * The result rows of join queries have no meaningful single resource
+     * location, so we'll just default to the root node for all such rows.
+     *
+     * @param row query result row
+     * @param join flag to indicate a join query
+     * @return resource location of the row
+     */
+    private String getHref(String path) throws RepositoryException {
+        DavResourceLocator l = locator.getFactory().createResourceLocator(
+                locator.getPrefix(), locator.getWorkspacePath(), path, false);
+        return l.getHref(true);
+    }
+
+    private String encode(String[] names) {
+        StringBuilder builder = new StringBuilder();
+        String delim = "";
+        for (String name : names) {
+            builder.append(delim);
+            builder.append(ISO9075.encode(name));
+            delim = " ";
+        }
+        return builder.toString();
     }
 
     private static String[] createSelectorNames(Iterable<RowValue> rows)
@@ -376,31 +400,4 @@ public class SearchResourceImpl implements SearchResource {
         }
     }
 
-    private static void collectSelectorNames(Query query,
-                                             QueryResult result,
-                                             List<String> sn) throws RepositoryException {
-        if (query instanceof QueryObjectModel) {
-            QueryObjectModel qom = (QueryObjectModel) query;
-            collectSelectorNames(qom.getSource(), sn);
-        } else {
-            sn.addAll(Arrays.asList(result.getSelectorNames()));
-        }
-    }
-
-    private static void collectSelectorNames(Source source, List<String> sn) {
-        if (source instanceof Join) {
-            collectSelectorNames((Join) source, sn);
-        } else {
-            collectSelectorNames((Selector) source, sn);
-        }
-    }
-
-    private static void collectSelectorNames(Join join, List<String> sn) {
-        collectSelectorNames(join.getLeft(), sn);
-        collectSelectorNames(join.getRight(), sn);
-    }
-
-    private static void collectSelectorNames(Selector s, List<String> sn) {
-        sn.add(s.getSelectorName());
-    }
 }
\ No newline at end of file
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/security/JcrSupportedPrivilegesProperty.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/security/JcrSupportedPrivilegesProperty.java
new file mode 100644
index 0000000..af8e870
--- /dev/null
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/security/JcrSupportedPrivilegesProperty.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.jcr.security;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.util.Text;
+import org.apache.jackrabbit.webdav.security.SupportedPrivilege;
+import org.apache.jackrabbit.webdav.security.SupportedPrivilegeSetProperty;
+import org.apache.jackrabbit.webdav.xml.Namespace;
+
+/**
+ * JcrSupportedPrivilegesProperty...
+ */
+public class JcrSupportedPrivilegesProperty {
+
+    private final Session session;
+    private final String absPath;
+    private final Set<Privilege> privileges = new HashSet<Privilege>();
+
+    private final Map<String, SupportedPrivilege> supportedPrivileges = new HashMap<String, SupportedPrivilege>();
+    private final HashSet<String> aggregated = new HashSet<String>();
+
+    /**
+     * Build supported privileges for the jcr:all privilege.
+     *
+     * @param session The reading session
+     */
+    public JcrSupportedPrivilegesProperty(Session session) throws RepositoryException {
+        this.session = session;
+        this.absPath = null;
+        AccessControlManager acMgr = session.getAccessControlManager();
+        Privilege jcrAll = acMgr.privilegeFromName(Privilege.JCR_ALL);
+        privileges.add(jcrAll);
+    }
+
+    /**
+     * @param session The reading session
+     * @param absPath An absolute path to an existing JCR node or {@code null}.
+     */
+    public JcrSupportedPrivilegesProperty(Session session, String absPath) {
+        this.session = session;
+        this.absPath = absPath;
+    }
+
+    /**
+     * Calculated the supported privileges at {@code absPath} and build a
+     * {@link org.apache.jackrabbit.webdav.security.SupportedPrivilegeSetProperty}
+     * from the result.
+     *
+     * @return a new {@code SupportedPrivilegeSetProperty} property.
+     * @throws RepositoryException
+     */
+    public SupportedPrivilegeSetProperty asDavProperty() throws RepositoryException {
+        if (privileges.isEmpty()) {
+            AccessControlManager acMgr = session.getAccessControlManager();
+            privileges.addAll(Arrays.asList(acMgr.getSupportedPrivileges(absPath)));
+        }
+        for (Privilege p : privileges) {
+            if (!aggregated.contains(p.getName())) {
+                createSupportedPrivilege(p);
+            }
+        }
+        return new SupportedPrivilegeSetProperty(supportedPrivileges.values().toArray(new SupportedPrivilege[supportedPrivileges.size()]));
+    }
+
+    private SupportedPrivilege createSupportedPrivilege(Privilege privilege) throws RepositoryException {
+        String privilegeName = privilege.getName();
+
+        String localName = Text.getLocalName(privilegeName);
+        String prefix = Text.getNamespacePrefix(privilegeName);
+        Namespace ns = (prefix.isEmpty()) ? Namespace.EMPTY_NAMESPACE : Namespace.getNamespace(prefix, session.getNamespaceURI(prefix));
+        org.apache.jackrabbit.webdav.security.Privilege davPrivilege = org.apache.jackrabbit.webdav.security.Privilege.getPrivilege(localName, ns);
+
+        SupportedPrivilege[] aggregates = (privilege.isAggregate()) ? getDeclaredAggregates(privilege) : null;
+
+        SupportedPrivilege sp = new SupportedPrivilege(davPrivilege, null, null, privilege.isAbstract(), aggregates);
+        if (!aggregated.contains(privilegeName)) {
+            supportedPrivileges.put(privilegeName, sp);
+        }
+        return sp;
+    }
+
+    private SupportedPrivilege[] getDeclaredAggregates(Privilege privilege) throws RepositoryException {
+        List<SupportedPrivilege> declAggr = new ArrayList<SupportedPrivilege>();
+        for (Privilege decl : privilege.getDeclaredAggregatePrivileges()) {
+            String name = decl.getName();
+            if (aggregated.add(name)) {
+                if (supportedPrivileges.containsKey(name)) {
+                    declAggr.add(supportedPrivileges.remove(name));
+                } else {
+                    declAggr.add(createSupportedPrivilege(decl));
+                }
+            }
+        }
+        return declAggr.toArray(new SupportedPrivilege[declAggr.size()]);
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/security/JcrUserPrivilegesProperty.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/security/JcrUserPrivilegesProperty.java
new file mode 100644
index 0000000..2fd963f
--- /dev/null
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/security/JcrUserPrivilegesProperty.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.jcr.security;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.util.Text;
+import org.apache.jackrabbit.webdav.security.CurrentUserPrivilegeSetProperty;
+import org.apache.jackrabbit.webdav.security.Privilege;
+import org.apache.jackrabbit.webdav.xml.Namespace;
+
+/**
+ * JcrPrivilegesProperty...
+ */
+public class JcrUserPrivilegesProperty {
+
+    private final Session session;
+    private final String absPath;
+
+    /**
+     *
+     * @param session The reading session
+     * @param absPath An absolute path to an existing JCR node or {@code null}.
+     * @param session1
+     */
+    public JcrUserPrivilegesProperty(Session session, String absPath) throws RepositoryException {
+        this.session = session;
+        this.absPath = absPath;
+    }
+
+    public CurrentUserPrivilegeSetProperty asDavProperty() throws RepositoryException {
+        List<Privilege> davPrivs = new ArrayList<Privilege>();
+        for (javax.jcr.security.Privilege privilege : session.getAccessControlManager().getPrivileges(absPath)) {
+            String privilegeName = privilege.getName();
+
+            String prefix = Text.getNamespacePrefix(privilegeName);
+            Namespace ns = (prefix.isEmpty()) ? Namespace.EMPTY_NAMESPACE : Namespace.getNamespace(prefix, session.getNamespaceURI(prefix));
+            davPrivs.add(Privilege.getPrivilege(Text.getLocalName(privilegeName), ns));
+        }
+
+        return new CurrentUserPrivilegeSetProperty(davPrivs.toArray(new Privilege[davPrivs.size()]));
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/security/SecurityUtils.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/security/SecurityUtils.java
new file mode 100644
index 0000000..8997e6a
--- /dev/null
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/security/SecurityUtils.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.jcr.security;
+
+import javax.jcr.Repository;
+import javax.jcr.Session;
+
+public final class SecurityUtils {
+
+    private SecurityUtils() {}
+
+    public static boolean supportsAccessControl(Session session) {
+        String desc = session.getRepository().getDescriptor(Repository.OPTION_ACCESS_CONTROL_SUPPORTED);
+        return Boolean.valueOf(desc);
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/transaction/TxLockManagerImpl.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/transaction/TxLockManagerImpl.java
index 5f2b1fa..c8a88e8 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/transaction/TxLockManagerImpl.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/transaction/TxLockManagerImpl.java
@@ -53,7 +53,7 @@ import java.util.Map;
 /**
  * <code>TxLockManagerImpl</code> manages locks with locktype
  * '{@link TransactionConstants#TRANSACTION dcr:transaction}'.
- * <p/>
+ * <p>
  */
  //todo: removing all expired locks
  //todo: 'local' and 'global' are not accurate terms in the given context > replace
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/version/report/ExportViewReport.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/version/report/ExportViewReport.java
index 1ffbedc..1fc16f0 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/version/report/ExportViewReport.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/version/report/ExportViewReport.java
@@ -48,7 +48,7 @@ import java.io.IOException;
  * {@link Session#exportDocumentView(String, java.io.OutputStream, boolean, boolean) DocView}
  * and {@link Session#exportSystemView(String, java.io.OutputStream, boolean, boolean) SysView}
  * of the {@link javax.jcr.Item item} represented by the requested resource.
- * <p/>
+ * <p>
  * The request body must contain a {@link ItemResourceConstants#NAMESPACE dcr}:exportview
  * element:
  * <pre>
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/version/report/JcrPrivilegeReport.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/version/report/JcrPrivilegeReport.java
index 74da086..069bc38 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/version/report/JcrPrivilegeReport.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/version/report/JcrPrivilegeReport.java
@@ -39,10 +39,20 @@ import org.w3c.dom.Document;
 import javax.jcr.RepositoryException;
 import java.util.List;
 import java.util.ArrayList;
-import java.security.AccessControlException;
 
 /**
- * <code>JcrPrivilegeReport</code>...
+ * <p>Report to retrieve the permissions granted to the reading session as defined
+ * by {@link javax.jcr.Session#hasPermission(String, String)}.</p>
+ *
+ * <p>NOTE: the name of this report and the names of the privileges are
+ * misleading as they rather correspond to the <i>actions</i> defined by
+ * {@link javax.jcr.Session}; while the JCR privileges s.str. have only been
+ * specified as of JSR 283 in the {@link javax.jcr.security.Privilege} interface.
+ * A better name would have been <strong>JcrActionReport</strong></p>
+ *
+ * @see org.apache.jackrabbit.webdav.jcr.security.JcrUserPrivilegesProperty for
+ * the webdav correspondence to {@link javax.jcr.security.AccessControlManager#getPrivileges(String)}
+ * mapped to the {@link org.apache.jackrabbit.webdav.security.CurrentUserPrivilegeSetProperty}.
  */
 public class JcrPrivilegeReport extends AbstractJcrReport {
 
@@ -67,6 +77,7 @@ public class JcrPrivilegeReport extends AbstractJcrReport {
      * @return {@link #PRIVILEGES_REPORT}
      * @see org.apache.jackrabbit.webdav.version.report.Report#getType()
      */
+    @Override
     public ReportType getType() {
         return PRIVILEGES_REPORT;
     }
@@ -77,6 +88,7 @@ public class JcrPrivilegeReport extends AbstractJcrReport {
      * @return true
      * @see org.apache.jackrabbit.webdav.version.report.Report#isMultiStatusReport()
      */
+    @Override
     public boolean isMultiStatusReport() {
         return true;
     }
@@ -95,6 +107,7 @@ public class JcrPrivilegeReport extends AbstractJcrReport {
         // immediately build the final multistatus element
         Element hrefElem = info.getContentElement(DavConstants.XML_HREF, DavConstants.NAMESPACE);
         String href = DomUtil.getTextTrim(hrefElem);
+        href = obtainAbsolutePathFromUri(href); // TODO: we should check whether the authority component matches
         DavResourceLocator resourceLoc = resource.getLocator();
         DavResourceLocator loc = resourceLoc.getFactory().createResourceLocator(resourceLoc.getPrefix(), href);
         // immediately build the final multistatus element
@@ -108,6 +121,7 @@ public class JcrPrivilegeReport extends AbstractJcrReport {
      * @return Xml element representing the output of the specified view.
      * @see org.apache.jackrabbit.webdav.xml.XmlSerializable#toXml(Document)
      */
+    @Override
     public Element toXml(Document document) {
         return ms.toXml(document);
     }
@@ -118,11 +132,9 @@ public class JcrPrivilegeReport extends AbstractJcrReport {
         List<Privilege> currentPrivs = new ArrayList<Privilege>();
         for (Privilege priv : PRIVS) {
             try {
-                getRepositorySession().checkPermission(repositoryPath, priv.getName());
-                currentPrivs.add(priv);
-            } catch (AccessControlException e) {
-                // ignore
-                log.debug(e.toString());
+                if (getRepositorySession().hasPermission(repositoryPath, priv.getName())) {
+                    currentPrivs.add(priv);
+                }
             } catch (RepositoryException e) {
                 // ignore
                 log.debug(e.toString());
@@ -131,4 +143,20 @@ public class JcrPrivilegeReport extends AbstractJcrReport {
         resp.add(new CurrentUserPrivilegeSetProperty(currentPrivs.toArray(new Privilege[currentPrivs.size()])));
         ms.addResponse(resp);
     }
+
+    private static String obtainAbsolutePathFromUri(String uri) {
+        try {
+            java.net.URI u = new java.net.URI(uri);
+            StringBuilder sb = new StringBuilder();
+            sb.append(u.getRawPath());
+            if (u.getRawQuery() != null) {
+                sb.append("?").append(u.getRawQuery());
+            }
+            return sb.toString();
+        }
+        catch (java.net.URISyntaxException ex) {
+            log.warn("parsing " + uri, ex);
+            return uri;
+        }
+    }
 }
\ No newline at end of file
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/version/report/LocateByUuidReport.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/version/report/LocateByUuidReport.java
index 2ce35e9..3d0208a 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/version/report/LocateByUuidReport.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/version/report/LocateByUuidReport.java
@@ -40,7 +40,7 @@ import javax.jcr.RepositoryException;
 /**
  * <code>LocateByUuidReport</code> handles REPORT requests for the 'locate-by-uuid'
  * report.
- * <p/>
+ * <p>
  * The request body must be a 'dcr:locate-by-uuid' XML element:
  * <pre>
  * <!ELEMENT locate-by-uuid ( href , prop? ) >
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/version/report/LocateCorrespondingNodeReport.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/version/report/LocateCorrespondingNodeReport.java
index ce540b3..4bd9099 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/version/report/LocateCorrespondingNodeReport.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/version/report/LocateCorrespondingNodeReport.java
@@ -44,7 +44,7 @@ import javax.jcr.Session;
  * <code>LocateCorrespondingNodeReport</code> is used to identify the resource that
  * represents the corresponding node in another workspace.
  *
- * <p/>
+ * <p>
  * The request body must be a 'dcr:locate-corresponding-node' XML element, that
  * contains the href of the source workspace, where the corresponding node should
  * be searched:
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/DavResourceImpl.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/DavResourceImpl.java
index f380479..1493c2c 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/DavResourceImpl.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/DavResourceImpl.java
@@ -71,6 +71,7 @@ import javax.jcr.Node;
 import javax.jcr.NodeIterator;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.Workspace;
 import javax.jcr.lock.Lock;
 import java.io.IOException;
@@ -569,7 +570,7 @@ public class DavResourceImpl implements DavResource, BindableResource, JcrConsta
 
             // make sure, non-jcr locks are removed, once the removal is completed
             try {
-                if (!isJsrLockable()) {
+                if (!isJcrLockable()) {
                     ActiveLock lock = getLock(Type.WRITE, Scope.EXCLUSIVE);
                     if (lock != null) {
                         lockManager.releaseLock(lock.getToken(), member);
@@ -653,6 +654,11 @@ public class DavResourceImpl implements DavResource, BindableResource, JcrConsta
                     Lock jcrLock = node.getLock();
                     if (jcrLock != null && jcrLock.isLive()) {
                         lock = new JcrActiveLock(jcrLock);
+                        String lockroot = locator
+                                .getFactory()
+                                .createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(),
+                                        jcrLock.getNode().getPath(), false).getHref(false);
+                        lock.setLockroot(lockroot);
                     }
                 }
             } catch (RepositoryException e) {
@@ -683,10 +689,17 @@ public class DavResourceImpl implements DavResource, BindableResource, JcrConsta
         ActiveLock lock = null;
         if (isLockable(lockInfo.getType(), lockInfo.getScope())) {
             // TODO: deal with existing locks, that may have been created, before the node was jcr-lockable...
-            if (isJsrLockable()) {
+            if (isJcrLockable()) {
                 try {
+                    javax.jcr.lock.LockManager lockMgr = node.getSession().getWorkspace().getLockManager();
+                    long timeout = lockInfo.getTimeout();
+                    if (timeout == LockInfo.INFINITE_TIMEOUT) {
+                        timeout = Long.MAX_VALUE;
+                    } else {
+                        timeout = timeout / 1000;
+                    }
                     // try to execute the lock operation
-                    Lock jcrLock = node.lock(lockInfo.isDeep(), false);
+                    Lock jcrLock = lockMgr.lock(node.getPath(), lockInfo.isDeep(), false, timeout, lockInfo.getOwner());
                     if (jcrLock != null) {
                         lock = new JcrActiveLock(jcrLock);
                     }
@@ -855,6 +868,8 @@ public class DavResourceImpl implements DavResource, BindableResource, JcrConsta
                 }
                 return ps;
             }
+        } catch (UnsupportedRepositoryOperationException e) {
+            log.debug("unable to calculate parent set", e);
         } catch (RepositoryException e) {
             log.warn("unable to calculate parent set", e);
         }
@@ -922,7 +937,7 @@ public class DavResourceImpl implements DavResource, BindableResource, JcrConsta
      *
      * @return true if this resource is lockable.
      */
-    private boolean isJsrLockable() {
+    private boolean isJcrLockable() {
         boolean lockable = false;
         if (exists()) {
             try {
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/DeltaVResourceImpl.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/DeltaVResourceImpl.java
index dadffdb..bedd5a4 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/DeltaVResourceImpl.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/DeltaVResourceImpl.java
@@ -22,6 +22,7 @@ import javax.jcr.Item;
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
 import javax.jcr.Property;
+import javax.xml.parsers.ParserConfigurationException;
 
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.webdav.DavCompliance;
@@ -44,8 +45,10 @@ import org.apache.jackrabbit.webdav.version.report.Report;
 import org.apache.jackrabbit.webdav.version.report.ReportInfo;
 import org.apache.jackrabbit.webdav.version.report.ReportType;
 import org.apache.jackrabbit.webdav.version.report.SupportedReportSetProperty;
+import org.apache.jackrabbit.webdav.xml.DomUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.w3c.dom.Element;
 
 /**
  * The <code>DeltaVResourceImpl</code> encapsulates the functionality common to all
@@ -117,12 +120,17 @@ public class DeltaVResourceImpl extends DavResourceImpl implements DeltaVResourc
             throw new DavException(DavServletResponse.SC_NOT_FOUND);
         }
 
-        if (supportedReports.isSupportedReport(reportInfo)) {
-            Report report = ReportType.getType(reportInfo).createReport(this, reportInfo);
-            return report;
-        } else {
-            throw new DavException(DavServletResponse.SC_UNPROCESSABLE_ENTITY, "Unknown report "+ reportInfo.getReportName() +"requested.");
+        if (!supportedReports.isSupportedReport(reportInfo)) {
+            Element condition = null;
+            try {
+                condition = DomUtil.createDocument().createElementNS("DAV:", "supported-report");
+            } catch (ParserConfigurationException ex) {
+                // we don't care THAT much
+            }
+            throw new DavException(DavServletResponse.SC_CONFLICT,
+                    "Unknown report '" + reportInfo.getReportName() + "' requested.", null, condition);
         }
+        return ReportType.getType(reportInfo).createReport(this, reportInfo);
     }
 
     /**
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/LocatorFactoryImplEx.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/LocatorFactoryImplEx.java
index 5b2b4bd..3e5b2b6 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/LocatorFactoryImplEx.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/LocatorFactoryImplEx.java
@@ -51,7 +51,7 @@ public class LocatorFactoryImplEx extends AbstractLocatorFactory {
             String repositoryPath = resourcePath.substring(wspPath.length());
             return (repositoryPath.length() == 0) ? "/" : repositoryPath;
         } else {
-            throw new IllegalArgumentException("Unexpected format of resource path.");
+            throw new IllegalArgumentException("Unexpected format of resource path: " + resourcePath + " (workspace: " + wspPath + ")");
         }
     }
 
diff --git a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/VersionControlledResourceImpl.java b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/VersionControlledResourceImpl.java
index 2a30a41..d98a279 100644
--- a/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/VersionControlledResourceImpl.java
+++ b/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/VersionControlledResourceImpl.java
@@ -330,7 +330,7 @@ public class VersionControlledResourceImpl extends DeltaVResourceImpl
                     properties.add(new HrefProperty(VERSION_HISTORY, vhHref, true));
 
                     // DAV:auto-version property: there is no auto version, explicit CHECKOUT is required.
-                    properties.add(new DefaultDavProperty(AUTO_VERSION, null, false));
+                    properties.add(new DefaultDavProperty(AUTO_VERSION, null, true));
 
                     // baseVersion -> used for DAV:checked-out or DAV:checked-in
                     String baseVHref = getLocatorFromNode(n.getBaseVersion()).getHref(false);
@@ -342,7 +342,7 @@ public class VersionControlledResourceImpl extends DeltaVResourceImpl
                             for (int i = 0; i < pv.length; i++) {
                                 predecessors[i] = n.getSession().getNodeByIdentifier(pv[i].getString());
                             }
-                            properties.add(getHrefProperty(VersionResource.PREDECESSOR_SET, predecessors, false, false));
+                            properties.add(getHrefProperty(VersionResource.PREDECESSOR_SET, predecessors, true, false));
                         }
                         // DAV:checked-out property (protected)
                         properties.add(new HrefProperty(CHECKED_OUT, baseVHref, true));
diff --git a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/server/remoting/davex/JsonDiffHandlerImportTest.java b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/server/remoting/davex/JsonDiffHandlerImportTest.java
new file mode 100644
index 0000000..d92643e
--- /dev/null
+++ b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/server/remoting/davex/JsonDiffHandlerImportTest.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.server.remoting.davex;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.security.AccessControlEntry;
+import javax.jcr.security.AccessControlList;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.commons.jackrabbit.authorization.AccessControlUtils;
+import org.apache.jackrabbit.core.security.principal.EveryonePrincipal;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+
+/**
+ * Tests for {@code JsonDiffHandler} that trigger the import mode.
+ */
+public class JsonDiffHandlerImportTest extends AbstractJCRTest {
+
+    private static final String JSOP_POLICY_TREE = "+rep:policy : {"
+            + "\"jcr:primaryType\" : \"rep:ACL\","
+            + "\"allow\" : {" + "\"jcr:primaryType\" : \"rep:GrantACE\","
+            + "\"rep:principalName\" : \"everyone\","
+            + "\"rep:privileges\" : [\"jcr:write\"]" + "}" + "}";
+
+    private static final List<String> ADD_NODES = new ArrayList<String>();
+    static {
+        ADD_NODES.add(
+                "+node1 : {"
+                        +"\"jcr:primaryType\" : \"nt:file\","
+                        + "\"jcr:mixinTypes\" : [\"rep:AccessControllable\"],"
+                        +"\"jcr:uuid\" : \"0a0ca2e9-ab98-4433-a12b-d57283765207\","
+                        +"\"rep:policy\" : {"
+                        +"\"jcr:primaryType\" : \"rep:ACL\","
+                        +"\"deny0\" : {"
+                        +"\"jcr:primaryType\" : \"rep:DenyACE\","
+                        +"\"rep:principalName\" : \"everyone\","
+                        +"\"rep:privileges\" : [\"jcr:read\"]"
+                        +"}"+"}"+"}");
+        ADD_NODES.add(
+                "+node2 : {"
+                        +"\"jcr:primaryType\" : \"nt:unstructured\","
+                        + "\"jcr:mixinTypes\" : [\"rep:AccessControllable\"],"
+                        +"\"rep:policy\" : {"
+                        +"\"jcr:primaryType\" : \"rep:ACL\","
+                        +"\"allow\" : {"
+                        +"\"jcr:primaryType\" : \"rep:GrantACE\","
+                        +"\"rep:principalName\" : \"everyone\","
+                        +"\"rep:privileges\" : [\"jcr:read\"]"
+                        +"},"
+                        +"\"deny\" : {"
+                        +"\"jcr:primaryType\" : \"rep:DenyACE\","
+                        +"\"rep:principalName\" : \"everyone\","
+                        +"\"rep:privileges\" : [\"jcr:write\"]"
+                        +"}"
+                        +"}"+"}");
+
+    }
+
+    private AccessControlManager acMgr;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        acMgr = superuser.getAccessControlManager();
+    }
+
+    private static void assertPolicy(AccessControlManager acMgr, Node targetNode, int noACEs) throws RepositoryException {
+        AccessControlPolicy[] policies = acMgr.getPolicies(targetNode.getPath());
+        assertEquals(policies.length, 1);
+
+        AccessControlPolicy acl = policies[0];
+        assertTrue(acl instanceof JackrabbitAccessControlList);
+        AccessControlEntry[] entries = ((JackrabbitAccessControlList) acl).getAccessControlEntries();
+        assertEquals(noACEs, entries.length);
+    }
+
+    /**
+     * Test two subsequent DIFF strings with policies, thus multiple addNode operations.
+     */
+    public void testMultipleAddNodeOperations() throws Exception {
+        for(String jsonString : ADD_NODES) {
+            JsonDiffHandler h = new JsonDiffHandler(superuser, testRoot, null);
+            new DiffParser(h).parse(jsonString);
+        }
+
+        assertPolicy(acMgr, testRootNode.getNode("node1"), 1);
+        assertPolicy(acMgr, testRootNode.getNode("node2"), 2);
+    }
+
+    /**
+     * Test adding 'rep:policy' policy node as a child node of /testroot without
+     * intermediate node.
+     */
+    public void testAllPolicyNode() throws Exception {
+        try {
+            testRootNode.addMixin("rep:AccessControllable");
+
+            JsonDiffHandler handler = new JsonDiffHandler(superuser, testRoot, null);
+            new DiffParser(handler).parse(JSOP_POLICY_TREE);
+
+            assertTrue(testRootNode.hasNode("rep:policy"));
+            assertTrue(testRootNode.getNode("rep:policy").getDefinition().isProtected());
+
+            assertTrue(testRootNode.getNode("rep:policy").getPrimaryNodeType()
+                    .getName().equals("rep:ACL"));
+
+            assertPolicy(acMgr, testRootNode, 1);
+
+            AccessControlEntry entry = ((AccessControlList) acMgr.getPolicies(testRoot)[0]).getAccessControlEntries()[0];
+            assertEquals(EveryonePrincipal.NAME, entry.getPrincipal().getName());
+            assertEquals(1, entry.getPrivileges().length);
+            assertEquals(acMgr.privilegeFromName(Privilege.JCR_WRITE), entry.getPrivileges()[0]);
+
+            if (entry instanceof JackrabbitAccessControlEntry) {
+                assertTrue(((JackrabbitAccessControlEntry) entry).isAllow());
+            }
+
+        } finally {
+            superuser.refresh(false);
+        }
+    }
+
+    /**
+     * Test adding 'rep:policy' policy node as a child node of /testroot without
+     * intermediate node.
+     */
+    public void testUpdatePolicyNode() throws Exception {
+        try {
+            AccessControlUtils.addAccessControlEntry(superuser, testRoot, EveryonePrincipal.getInstance(), new String[] {Privilege.JCR_READ}, false);
+
+            JsonDiffHandler handler = new JsonDiffHandler(superuser, testRoot, null);
+            new DiffParser(handler).parse(JSOP_POLICY_TREE);
+
+            assertTrue(testRootNode.hasNode("rep:policy"));
+            assertTrue(testRootNode.getNode("rep:policy").getDefinition().isProtected());
+
+            assertTrue(testRootNode.getNode("rep:policy").getPrimaryNodeType()
+                    .getName().equals("rep:ACL"));
+
+            assertPolicy(acMgr, testRootNode, 1);
+
+            AccessControlEntry entry = ((AccessControlList) acMgr.getPolicies(testRoot)[0]).getAccessControlEntries()[0];
+            assertEquals(EveryonePrincipal.NAME, entry.getPrincipal().getName());
+            assertEquals(1, entry.getPrivileges().length);
+            assertEquals(acMgr.privilegeFromName(Privilege.JCR_WRITE), entry.getPrivileges()[0]);
+
+            if (entry instanceof JackrabbitAccessControlEntry) {
+                assertTrue(((JackrabbitAccessControlEntry) entry).isAllow());
+            }
+
+        } finally {
+            superuser.refresh(false);
+        }
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/server/remoting/davex/JsonDiffHandlerTest.java b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/server/remoting/davex/JsonDiffHandlerTest.java
index 7903da4..9fc7a88 100644
--- a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/server/remoting/davex/JsonDiffHandlerTest.java
+++ b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/server/remoting/davex/JsonDiffHandlerTest.java
@@ -72,165 +72,196 @@ public class JsonDiffHandlerTest extends TestCase {
 
     private final class DummySession implements Session {
 
+        @Override
         public Repository getRepository() {
             return null;
         }
 
+        @Override
         public String getUserID() {
             return null;
         }
 
+        @Override
         public Object getAttribute(String name) {
             return null;
         }
 
+        @Override
         public String[] getAttributeNames() {
             return new String[0];
         }
 
+        @Override
         public Workspace getWorkspace() {
             return null;
         }
 
+        @Override
         public Session impersonate(Credentials credentials) {
             return null;
         }
 
+        @Override
         public Node getRootNode() {
             return null;
         }
 
+        @Override
         public Node getNodeByUUID(String uuid) {
             return null;
         }
 
+        @Override
         public Item getItem(String absPath) {
             return null;
         }
 
+        @Override
         public boolean itemExists(String absPath) {
             return false;
         }
 
+        @Override
         public void move(String srcAbsPath, String destAbsPath) {
         }
 
+        @Override
         public void save() {
         }
 
+        @Override
         public void refresh(boolean keepChanges) {
         }
 
+        @Override
         public boolean hasPendingChanges() {
             return false;
         }
 
+        @Override
         public ValueFactory getValueFactory() {
             return null;
         }
 
+        @Override
         public void checkPermission(String absPath, String actions) throws AccessControlException {
         }
 
+        @Override
         public ContentHandler getImportContentHandler(String parentAbsPath, int uuidBehavior) {
             return null;
         }
 
+        @Override
         public void importXML(String parentAbsPath, InputStream in, int uuidBehavior) {
         }
 
+        @Override
         public void exportSystemView(String absPath, ContentHandler contentHandler, boolean skipBinary, boolean noRecurse) {
         }
 
+        @Override
         public void exportSystemView(String absPath, OutputStream out, boolean skipBinary, boolean noRecurse) {
         }
 
+        @Override
         public void exportDocumentView(String absPath, ContentHandler contentHandler, boolean skipBinary, boolean noRecurse) {
         }
 
+        @Override
         public void exportDocumentView(String absPath, OutputStream out, boolean skipBinary, boolean noRecurse) {
         }
 
+        @Override
         public void setNamespacePrefix(String prefix, String uri) {
         }
 
+        @Override
         public String[] getNamespacePrefixes() {
             return new String[0];
         }
 
+        @Override
         public String getNamespaceURI(String prefix) {
             return null;
         }
 
+        @Override
         public String getNamespacePrefix(String uri) {
             return null;
         }
 
+        @Override
         public void logout() {
         }
 
+        @Override
         public boolean isLive() {
             return false;
         }
 
+        @Override
         public void addLockToken(String lt) {
         }
 
+        @Override
         public String[] getLockTokens() {
             return new String[0];
         }
 
+        @Override
         public void removeLockToken(String lt) {
         }
 
+        @Override
         public AccessControlManager getAccessControlManager() {
-            // TODO Auto-generated method stub
             return null;
         }
 
+        @Override
         public Node getNode(String arg0) {
-            // TODO Auto-generated method stub
             return null;
         }
 
+        @Override
         public Node getNodeByIdentifier(String arg0) {
-            // TODO Auto-generated method stub
             return null;
         }
 
+        @Override
         public Property getProperty(String arg0) {
-            // TODO Auto-generated method stub
             return null;
         }
 
+        @Override
         public RetentionManager getRetentionManager() {
-            // TODO Auto-generated method stub
             return null;
         }
 
+        @Override
         public boolean hasCapability(String arg0, Object arg1, Object[] arg2) {
-            // TODO Auto-generated method stub
             return false;
         }
 
+        @Override
         public boolean hasPermission(String arg0, String arg1) {
-            // TODO Auto-generated method stub
             return false;
         }
 
+        @Override
         public boolean nodeExists(String arg0) {
-            // TODO Auto-generated method stub
             return false;
         }
 
+        @Override
         public boolean propertyExists(String arg0) {
-            // TODO Auto-generated method stub
             return false;
         }
 
+        @Override
         public void removeItem(String arg0) {
-            // TODO Auto-generated method stub
-            
+
         }
     }
 }
diff --git a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/jcr/LockTimeOutFormatTest.java b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/jcr/LockTimeOutFormatTest.java
new file mode 100644
index 0000000..8c634da
--- /dev/null
+++ b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/jcr/LockTimeOutFormatTest.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.jcr;
+
+import java.net.URISyntaxException;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
+import javax.xml.parsers.ParserConfigurationException;
+
+import junit.framework.TestCase;
+
+import org.apache.jackrabbit.webdav.jcr.lock.JcrActiveLock;
+import org.apache.jackrabbit.webdav.xml.DomUtil;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+/**
+ * <code>LockTimeOutFormatTest</code>...
+ */
+public class LockTimeOutFormatTest extends TestCase {
+
+    public void testOneSec() throws RepositoryException, URISyntaxException, ParserConfigurationException {
+        testfmt(1, "Second-1");
+    }
+
+    public void testInf() throws RepositoryException, URISyntaxException, ParserConfigurationException {
+        testfmt(Long.MAX_VALUE, "Infinite");
+    }
+
+    public void testTooLong() throws RepositoryException, URISyntaxException, ParserConfigurationException {
+        testfmt(Integer.MAX_VALUE + 100000L, "Infinite");
+    }
+
+    public void testNeg() throws RepositoryException, URISyntaxException, ParserConfigurationException {
+        // expired
+        testfmt(-1, null);
+    }
+
+    private void testfmt(long jcrtimeout, String expectedString) throws RepositoryException, URISyntaxException, ParserConfigurationException {
+
+        Lock l = new TestLock(jcrtimeout);
+        JcrActiveLock al = new JcrActiveLock(l);
+
+        Document d = DomUtil.createDocument();
+        Element activeLock = al.toXml(d);
+        assertEquals("activelock", activeLock.getLocalName());
+        NodeList nl = activeLock.getElementsByTagNameNS("DAV:", "timeout");
+
+        if (expectedString == null) {
+            assertEquals(0, nl.getLength());
+        }
+        else {
+            assertEquals(1, nl.getLength());
+            Element timeout = (Element)nl.item(0);
+            String t = DomUtil.getText(timeout);
+            assertEquals(expectedString, t);
+        }
+    }
+
+    /**
+     * Minimal Lock impl for tests above
+     */
+    private static class TestLock implements Lock {
+
+        private final long timeout;
+
+        public TestLock(long timeout) {
+            this.timeout = timeout;
+        }
+
+        public String getLockOwner() {
+            return null;
+        }
+
+        public boolean isDeep() {
+            return false;
+        }
+
+        public Node getNode() {
+            return null;
+        }
+
+        public String getLockToken() {
+            return "foo";
+        }
+
+        public long getSecondsRemaining() throws RepositoryException {
+            return timeout;
+        }
+
+        public boolean isLive() throws RepositoryException {
+            return timeout >= 0;
+        }
+
+        public boolean isSessionScoped() {
+            return false;
+        }
+
+        public boolean isLockOwningSession() {
+            return false;
+        }
+
+        public void refresh() throws LockException, RepositoryException {
+        }
+    }
+}
diff --git a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/jcr/LockTokenMappingTest.java b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/jcr/LockTokenMappingTest.java
new file mode 100644
index 0000000..4b40748
--- /dev/null
+++ b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/jcr/LockTokenMappingTest.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.jcr;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.UUID;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
+
+import org.apache.jackrabbit.webdav.jcr.lock.LockTokenMapper;
+
+import junit.framework.TestCase;
+
+/**
+ * <code>LockTokenMappingTest</code>...
+ */
+public class LockTokenMappingTest extends TestCase {
+
+    // test lock with a lock token similar to the ones assigned by Jackrabbit
+    public void testOpenScopedJcr() throws RepositoryException, URISyntaxException {
+        testRoundtrip(UUID.randomUUID().toString() + "-X");
+    }
+
+    // test a fancy lock string
+    public void testOpenScopedFancy() throws RepositoryException, URISyntaxException {
+        testRoundtrip("\n\u00c4 \u20ac");
+    }
+
+    private void testRoundtrip(String token) throws RepositoryException, URISyntaxException {
+
+        Lock l = new TestLock(token);
+        String davtoken = LockTokenMapper.getDavLocktoken(l);
+
+        // valid URI?
+        URI u = new URI(davtoken);
+        assertTrue("lock token must be absolute URI", u.isAbsolute());
+        assertEquals("lock token URI must be all-ASCII", u.toASCIIString(), u.toString());
+
+        String jcrtoken = LockTokenMapper.getJcrLockToken(davtoken);
+        assertEquals(jcrtoken, l.getLockToken());
+    }
+
+    /**
+     * Minimal Lock impl for tests above
+     */
+    private static class TestLock implements Lock {
+
+        private final String token;
+
+        public TestLock(String token) {
+            this.token = token;
+        }
+
+        public String getLockOwner() {
+            return null;
+        }
+
+        public boolean isDeep() {
+            return false;
+        }
+
+        public Node getNode() {
+            return null;
+        }
+
+        public String getLockToken() {
+            return token;
+        }
+
+        public long getSecondsRemaining() throws RepositoryException {
+            return 0;
+        }
+
+        public boolean isLive() throws RepositoryException {
+            return false;
+        }
+
+        public boolean isSessionScoped() {
+            return false;
+        }
+
+        public boolean isLockOwningSession() {
+            return false;
+        }
+
+        public void refresh() throws LockException, RepositoryException {
+        }
+    }
+}
diff --git a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/jcr/security/AbstractSecurityTest.java b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/jcr/security/AbstractSecurityTest.java
new file mode 100644
index 0000000..7d24079
--- /dev/null
+++ b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/jcr/security/AbstractSecurityTest.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.jcr.security;
+
+import javax.jcr.Repository;
+import javax.jcr.security.AccessControlManager;
+
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.jackrabbit.test.NotExecutableException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractSecurityTest extends AbstractJCRTest {
+
+    protected AccessControlManager acMgr;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (isSupported(Repository.OPTION_ACCESS_CONTROL_SUPPORTED)) {
+            acMgr = superuser.getAccessControlManager();
+        } else {
+            throw new NotExecutableException();
+        }
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/jcr/security/JcrSupportedPrivilegePropertyTest.java b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/jcr/security/JcrSupportedPrivilegePropertyTest.java
new file mode 100644
index 0000000..6587770
--- /dev/null
+++ b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/jcr/security/JcrSupportedPrivilegePropertyTest.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.jcr.security;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.jcr.RepositoryException;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.webdav.security.SupportedPrivilege;
+
+public class JcrSupportedPrivilegePropertyTest extends AbstractSecurityTest {
+
+    public void testSupportedPrivileges() throws RepositoryException {
+        Set<Privilege> privs = new HashSet<Privilege>(Arrays.asList(acMgr.getSupportedPrivileges(testRoot)));
+        JcrSupportedPrivilegesProperty prop = new JcrSupportedPrivilegesProperty(superuser, testRoot);
+        List<SupportedPrivilege> value = prop.asDavProperty().getValue();
+
+        if (privs.contains(acMgr.privilegeFromName(Privilege.JCR_ALL))) {
+            assertEquals(1, value.size());
+        }
+    }
+
+    public void testJcrAllPrivilege() throws RepositoryException {
+        JcrSupportedPrivilegesProperty prop = new JcrSupportedPrivilegesProperty(superuser);
+        List<SupportedPrivilege> value = prop.asDavProperty().getValue();
+
+        assertEquals(1, value.size());
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/jcr/security/JcrUserPrivilegesPropertyTest.java b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/jcr/security/JcrUserPrivilegesPropertyTest.java
new file mode 100644
index 0000000..c861d28
--- /dev/null
+++ b/jackrabbit-jcr-server/src/test/java/org/apache/jackrabbit/webdav/jcr/security/JcrUserPrivilegesPropertyTest.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.jcr.security;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.security.AccessControlManager;
+
+import org.apache.jackrabbit.util.Text;
+import org.apache.jackrabbit.webdav.security.Privilege;
+import org.apache.jackrabbit.webdav.xml.Namespace;
+
+public class JcrUserPrivilegesPropertyTest extends AbstractSecurityTest {
+
+    private Set<Privilege> getExpected(AccessControlManager acMgr, Session s) throws RepositoryException {
+        Set<Privilege> expected = new HashSet<Privilege>();
+        for (javax.jcr.security.Privilege p : acMgr.getPrivileges(testRoot)) {
+            String localName = Text.getLocalName(p.getName());
+            String prefix = Text.getNamespacePrefix(p.getName());
+            Namespace ns = (prefix.isEmpty()) ? Namespace.EMPTY_NAMESPACE : Namespace.getNamespace(prefix, s.getNamespaceURI(prefix));
+            expected.add(Privilege.getPrivilege(localName, ns));
+        }
+        return expected;
+    }
+
+    public void testAdminPrivileges() throws RepositoryException {
+        Set<Privilege> expected = getExpected(acMgr, superuser);
+
+        JcrUserPrivilegesProperty upp = new JcrUserPrivilegesProperty(superuser, testRoot);
+        Collection<Privilege> davPrivs = upp.asDavProperty().getValue();
+
+        assertEquals(expected.size(), davPrivs.size());
+        assertTrue(davPrivs.containsAll(expected));
+    }
+
+    public void testReadOnlyPrivileges() throws RepositoryException {
+        Session readOnly = getHelper().getReadOnlySession();
+        try {
+            Set<Privilege> expected = getExpected(readOnly.getAccessControlManager(), readOnly);
+
+            JcrUserPrivilegesProperty upp = new JcrUserPrivilegesProperty(readOnly, testRoot);
+            Collection<Privilege> davPrivs = upp.asDavProperty().getValue();
+
+            assertEquals(expected.size(), davPrivs.size());
+            assertTrue(davPrivs.containsAll(expected));
+        } finally {
+            if (readOnly != null) {
+                readOnly.logout();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-jcr-server/src/test/resources/protectedHandlers.properties b/jackrabbit-jcr-server/src/test/resources/protectedHandlers.properties
new file mode 100644
index 0000000..f51e656
--- /dev/null
+++ b/jackrabbit-jcr-server/src/test/resources/protectedHandlers.properties
@@ -0,0 +1,17 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+# ProtectedItemRemoveHandler implementation class
+javax.jcr.tck.access.control.list.handler=org.apache.jackrabbit.server.remoting.davex.AclRemoveHandler
diff --git a/jackrabbit-jcr-server/src/test/resources/repository.xml b/jackrabbit-jcr-server/src/test/resources/repository.xml
new file mode 100644
index 0000000..95f050e
--- /dev/null
+++ b/jackrabbit-jcr-server/src/test/resources/repository.xml
@@ -0,0 +1,166 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 2.6//EN"
+                            "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<!-- Example Repository Configuration File -->
+<Repository>
+    <!--
+        virtual file system where the repository stores global state
+        (e.g. registered namespaces, custom node types, etc.)
+    -->
+    <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+        <param name="path" value="${rep.home}/repository"/>
+    </FileSystem>
+
+    <!--
+        data store configuration
+    -->
+    <DataStore class="org.apache.jackrabbit.core.data.FileDataStore"/>
+    <!--
+        sample database data store configuration
+        <DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
+            <param name="url" value="jdbc:h2:~/test"/>
+            <param name="user" value="sa"/>
+            <param name="password" value="sa"/>
+        </DataStore>
+    -->
+    
+    <!--
+        repository lock mechanism configuration
+    <RepositoryLockMechanism class="org.apache.jackrabbit.core.util.CooperativeFileLock"/>
+    -->
+
+    <!--
+        security configuration
+    -->
+    <Security appName="Jackrabbit">
+        <!--
+            security manager:
+            class: FQN of class implementing the JackrabbitSecurityManager interface
+        -->
+        <SecurityManager class="org.apache.jackrabbit.core.UserPerWorkspaceSecurityManager">
+            <!-- <param name="config" value="${rep.home}/security.xml"/> -->
+        </SecurityManager>
+
+        <!--
+            access manager:
+            class: FQN of class implementing the AccessManager interface
+        -->
+        <AccessManager class="org.apache.jackrabbit.core.security.DefaultAccessManager">
+            <!-- <param name="config" value="${rep.home}/access.xml"/> -->
+        </AccessManager>
+
+        <LoginModule class="org.apache.jackrabbit.core.security.authentication.DefaultLoginModule">
+           <!-- 
+              anonymous user name ('anonymous' is the default value)
+            -->
+           <param name="anonymousId" value="anonymous"/>
+           <!--
+              administrator user id (default value if param is missing is 'admin')
+            -->
+           <param name="adminId" value="admin"/>
+           <!--
+              optional parameter 'principalProvider'.
+              the value refers to the class name of the PrincipalProvider implementation.
+           -->
+           <!-- <param name="principalProvider" value="..."/> -->
+        </LoginModule>
+    </Security>
+
+    <!--
+        location of workspaces root directory and name of default workspace
+    -->
+    <Workspaces rootPath="${rep.home}/workspaces" defaultWorkspace="default" maxIdleTime="2"/>
+    <!--
+        workspace configuration template:
+        used to create the initial workspace if there's no workspace yet
+    -->
+    <Workspace name="${wsp.name}">
+        <!--
+            virtual file system of the workspace:
+            class: FQN of class implementing the FileSystem interface
+        -->
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${wsp.home}"/>
+        </FileSystem>
+        <!--
+            persistence manager of the workspace:
+            class: FQN of class implementing the PersistenceManager interface
+        -->
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+          <param name="url" value="jdbc:derby:${wsp.home}/db;create=true"/>
+          <param name="schemaObjectPrefix" value="${wsp.name}_"/>
+        </PersistenceManager>
+        <!--
+            Search index and the file system it uses.
+            class: FQN of class implementing the QueryHandler interface
+        -->
+        <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+            <param name="path" value="${wsp.home}/index"/>
+        </SearchIndex>
+
+        <!--
+            XML Import configuration of the workspace
+        -->
+        <Import>
+          <ProtectedNodeImporter class="org.apache.jackrabbit.core.xml.AccessControlImporter"/>
+          <ProtectedPropertyImporter class="org.apache.jackrabbit.core.security.user.UserImporter">
+             <param name="importBehavior" value="besteffort"/>
+          </ProtectedPropertyImporter>
+        </Import>
+    </Workspace>
+
+    <!--
+        Configures the versioning
+    -->
+    <Versioning rootPath="${rep.home}/version">
+        <!--
+            Configures the filesystem to use for versioning for the respective
+            persistence manager
+        -->
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${rep.home}/version" />
+        </FileSystem>
+
+        <!--
+            Configures the persistence manager to be used for persisting version state.
+            Please note that the current versioning implementation is based on
+            a 'normal' persistence manager, but this could change in future
+            implementations.
+        -->
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+          <param name="url" value="jdbc:derby:${rep.home}/version/db;create=true"/>
+          <param name="schemaObjectPrefix" value="version_"/>
+        </PersistenceManager>
+    </Versioning>
+
+    <!--
+        Search index for content that is shared repository wide
+        (/jcr:system tree, contains mainly versions)
+    -->
+    <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+        <param name="path" value="${rep.home}/repository/index"/>
+    </SearchIndex>
+    
+    <!--
+        Run with a cluster journal
+    -->
+    <Cluster id="node1">
+        <Journal class="org.apache.jackrabbit.core.journal.MemoryJournal"/>
+    </Cluster>
+</Repository>
diff --git a/jackrabbit-jcr-server/src/test/resources/repositoryStubImpl.properties b/jackrabbit-jcr-server/src/test/resources/repositoryStubImpl.properties
new file mode 100644
index 0000000..e932c14
--- /dev/null
+++ b/jackrabbit-jcr-server/src/test/resources/repositoryStubImpl.properties
@@ -0,0 +1,23 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+# Stub implementation class
+javax.jcr.tck.repository_stub_impl=org.apache.jackrabbit.core.JackrabbitRepositoryStub
+
+# the repository home
+org.apache.jackrabbit.repository.home=target/repository
+
+# the repository configuration
+org.apache.jackrabbit.repository.config=target/test-classes/repository.xml
diff --git a/jackrabbit-jcr-servlet/pom.xml b/jackrabbit-jcr-servlet/pom.xml
index 6a8ec4f..d0ea7f1 100644
--- a/jackrabbit-jcr-servlet/pom.xml
+++ b/jackrabbit-jcr-servlet/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>jackrabbit-parent</artifactId>
-    <version>2.3.6</version>
+    <version>2.10.1</version>
     <relativePath>../jackrabbit-parent/pom.xml</relativePath>
   </parent>
 
@@ -48,18 +48,18 @@
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-commons</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-rmi</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <optional>true</optional>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-core</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <optional>true</optional>
     </dependency>
   </dependencies>
diff --git a/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/AbstractRepositoryServlet.java b/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/AbstractRepositoryServlet.java
index 0500c9f..8de91fb 100644
--- a/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/AbstractRepositoryServlet.java
+++ b/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/AbstractRepositoryServlet.java
@@ -27,19 +27,16 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.jackrabbit.commons.repository.ProxyRepository;
-import org.apache.jackrabbit.commons.repository.RepositoryFactory;
 
 /**
  * Abstract base class for servlets that make a repository available in
  * the servlet context. This class handles the initialization and cleanup
  * tasks of setting up and clearing the configured repository attribute,
  * while a subclass only needs to implement the abstract
- * {@link #getRepositoryFactory()} method that returns a factory for
- * retrieving the actual content repository.
+ * {@link #getRepository()} method that returns the actual content repository.
  * <p>
  * The {@link Repository} instance bound to the servlet context is actually
- * a {@link ProxyRepository} that uses the given {@link RepositoryFactory}
- * for late binding of the underlying content repository.
+ * a {@link ProxyRepository} for late binding of the underlying content repository.
  * <p>
  * The default name of the repository attribute is
  * "<code>javax.jcr.Repository</code>", but it can be changed by specifying
@@ -67,11 +64,10 @@ import org.apache.jackrabbit.commons.repository.RepositoryFactory;
 public abstract class AbstractRepositoryServlet extends HttpServlet {
 
     /**
-     * Binds a {@link ProxyRepository} with the factory returned by
-     * {@link #getRepositoryFactory()} in the configured servlet
-     * context attribute.
+     * Binds a {@link ProxyRepository} with the repository returned by
+     * {@link #getRepository()} in the configured servlet context attribute.
      *
-     * @throws ServletException if the factory could not be retrieved
+     * @throws ServletException
      */
     public void init() throws ServletException {
         getServletContext().setAttribute(
diff --git a/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/ContextRepositoryServlet.java b/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/ContextRepositoryServlet.java
index 1b8a39b..4b9a7d8 100644
--- a/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/ContextRepositoryServlet.java
+++ b/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/ContextRepositoryServlet.java
@@ -93,12 +93,12 @@ public class ContextRepositoryServlet extends AbstractRepositoryServlet {
         } else if (repository != null) {
             throw new RepositoryException(
                     "Invalid repository: Attribute " + name
-                    + " in servet context " + otherContext.getServletContextName()
+                    + " in servlet context " + otherContext.getServletContextName()
                     + " is an instance of " + repository.getClass().getName());
         } else {
             throw new RepositoryException(
                     "Repository not found: Attribute " + name
-                    + " does not exist in servet context "
+                    + " does not exist in servlet context "
                     + otherContext.getServletContextName());
         }
     }
diff --git a/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/FilterRepositoryFactory.java b/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/FilterRepositoryFactory.java
index 8ab1322..3baad0a 100644
--- a/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/FilterRepositoryFactory.java
+++ b/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/FilterRepositoryFactory.java
@@ -1,95 +1,95 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.servlet;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletContext;
-
-import org.apache.jackrabbit.commons.repository.RepositoryFactory;
-
-/**
- * Factory that looks up a repository from the context of a given filter.
- * <p>
- * The default name of the repository attribute is
- * "<code>javax.jcr.Repository</code>", but it can be changed by specifying
- * an init parameter with the same name:
- * <pre>
- * <filter>
- *   <init-param>
- *     <param-name>javax.jcr.Repository</param-name>
- *     <param-value>my.repository.attribute</param-value>
- *     <description>
- *       This init parameter causes the repository to be looked up from
- *       the "my.repository.attribute" attribute instead of the default
- *       "javax.jcr.Repository".
- *     </description>
- *   </init-param>
- * </filter>
- * </pre>
- *
- * @since Apache Jackrabbit 1.6
- */
-public class FilterRepositoryFactory implements RepositoryFactory {
-
-    /**
-     * Configuration of the filter whose context contains the repository.
-     */
-    private final FilterConfig config;
-
-    /**
-     * Creates a factory for looking up a repository from the context
-     * associated with the given filter configuration.
-     *
-     * @param config filter configuration
-     */
-    public FilterRepositoryFactory(FilterConfig config) {
-        this.config = config;
-    }
-
-    /**
-     * Looks up and returns a repository bound in the servlet context of
-     * the given filter.
-     *
-     * @return repository from servlet context
-     * @throws RepositoryException if the repository is not available
-     */
-    public Repository getRepository() throws RepositoryException {
-        String name = config.getInitParameter(Repository.class.getName());
-        if (name == null) {
-            name = Repository.class.getName();
-        }
-
-        ServletContext context = config.getServletContext();
-        Object repository = context.getAttribute(name);
-        if (repository instanceof Repository) {
-            return (Repository) repository;
-        } else if (repository != null) {
-            throw new RepositoryException(
-                    "Invalid repository: Attribute " + name
-                    + " in servlet context " + context.getServletContextName()
-                    + " is an instance of " + repository.getClass().getName());
-        } else {
-            throw new RepositoryException(
-                    "Repository not found: Attribute " + name
-                    + " does not exist in servlet context "
-                    + context.getServletContextName());
-        }
-    }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.servlet;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletContext;
+
+import org.apache.jackrabbit.commons.repository.RepositoryFactory;
+
+/**
+ * Factory that looks up a repository from the context of a given filter.
+ * <p>
+ * The default name of the repository attribute is
+ * "<code>javax.jcr.Repository</code>", but it can be changed by specifying
+ * an init parameter with the same name:
+ * <pre>
+ * <filter>
+ *   <init-param>
+ *     <param-name>javax.jcr.Repository</param-name>
+ *     <param-value>my.repository.attribute</param-value>
+ *     <description>
+ *       This init parameter causes the repository to be looked up from
+ *       the "my.repository.attribute" attribute instead of the default
+ *       "javax.jcr.Repository".
+ *     </description>
+ *   </init-param>
+ * </filter>
+ * </pre>
+ *
+ * @since Apache Jackrabbit 1.6
+ */
+public class FilterRepositoryFactory implements RepositoryFactory {
+
+    /**
+     * Configuration of the filter whose context contains the repository.
+     */
+    private final FilterConfig config;
+
+    /**
+     * Creates a factory for looking up a repository from the context
+     * associated with the given filter configuration.
+     *
+     * @param config filter configuration
+     */
+    public FilterRepositoryFactory(FilterConfig config) {
+        this.config = config;
+    }
+
+    /**
+     * Looks up and returns a repository bound in the servlet context of
+     * the given filter.
+     *
+     * @return repository from servlet context
+     * @throws RepositoryException if the repository is not available
+     */
+    public Repository getRepository() throws RepositoryException {
+        String name = config.getInitParameter(Repository.class.getName());
+        if (name == null) {
+            name = Repository.class.getName();
+        }
+
+        ServletContext context = config.getServletContext();
+        Object repository = context.getAttribute(name);
+        if (repository instanceof Repository) {
+            return (Repository) repository;
+        } else if (repository != null) {
+            throw new RepositoryException(
+                    "Invalid repository: Attribute " + name
+                    + " in servlet context " + context.getServletContextName()
+                    + " is an instance of " + repository.getClass().getName());
+        } else {
+            throw new RepositoryException(
+                    "Repository not found: Attribute " + name
+                    + " does not exist in servlet context "
+                    + context.getServletContextName());
+        }
+    }
+
+}
diff --git a/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/login/AbstractLoginFilter.java b/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/login/AbstractLoginFilter.java
index cc45991..2da62cb 100644
--- a/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/login/AbstractLoginFilter.java
+++ b/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/login/AbstractLoginFilter.java
@@ -1,113 +1,113 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.servlet.login;
-
-import java.io.IOException;
-
-import javax.jcr.AccessDeniedException;
-import javax.jcr.Credentials;
-import javax.jcr.LoginException;
-import javax.jcr.NoSuchWorkspaceException;
-import javax.jcr.Node;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.jackrabbit.servlet.ServletRepository;
-
-/**
- *
- * @since Apache Jackrabbit 1.6
- */
-public abstract class AbstractLoginFilter implements Filter {
-
-    private Repository repository;
-
-    private String workspace;
-
-    private String sessionAttribute;
-
-    private String nodeAttribute;
-
-    public void init(FilterConfig config) {
-        repository = new ServletRepository(config);
-        workspace = config.getInitParameter("workspace");
-
-        sessionAttribute = config.getInitParameter(Session.class.getName());
-        if (sessionAttribute == null) {
-            sessionAttribute = Session.class.getName();
-        }
-
-        nodeAttribute = config.getInitParameter(Node.class.getName());
-        if (nodeAttribute == null) {
-            nodeAttribute = Node.class.getName();
-        }
-    }
-
-    public void destroy() {
-    }
-
-    public void doFilter(
-            ServletRequest request, ServletResponse response,
-            FilterChain chain) throws IOException, ServletException {
-        HttpServletRequest httpRequest = (HttpServletRequest) request;
-        HttpServletResponse httpResponse = (HttpServletResponse) response;
-        try {
-            Credentials credentials = getCredentials(httpRequest);
-            Session session = repository.login(credentials, workspace);
-            try {
-                request.setAttribute(sessionAttribute, session);
-                request.setAttribute(nodeAttribute, session.getRootNode());
-                chain.doFilter(request, response);
-                if (session.hasPendingChanges()) {
-                    session.save();
-                }
-            } finally {
-                session.logout();
-            }
-        } catch (ServletException e) {
-            Throwable cause = e.getRootCause();
-            if (cause instanceof AccessDeniedException) {
-                httpResponse.sendError(
-                        HttpServletResponse.SC_FORBIDDEN, cause.getMessage());
-            } else {
-                throw e;
-            }
-        } catch (LoginException e) {
-            httpResponse.sendError(
-                    HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
-        } catch (NoSuchWorkspaceException e) {
-            throw new ServletException(
-                    "Workspace " + workspace
-                    + " not found in the content repository", e);
-        } catch (RepositoryException e) {
-            throw new ServletException(
-                    "Unable to access the content repository", e);
-        }
-    }
-
-    protected abstract Credentials getCredentials(HttpServletRequest request);
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.servlet.login;
+
+import java.io.IOException;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.Credentials;
+import javax.jcr.LoginException;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.jackrabbit.servlet.ServletRepository;
+
+/**
+ *
+ * @since Apache Jackrabbit 1.6
+ */
+public abstract class AbstractLoginFilter implements Filter {
+
+    private Repository repository;
+
+    private String workspace;
+
+    private String sessionAttribute;
+
+    private String nodeAttribute;
+
+    public void init(FilterConfig config) {
+        repository = new ServletRepository(config);
+        workspace = config.getInitParameter("workspace");
+
+        sessionAttribute = config.getInitParameter(Session.class.getName());
+        if (sessionAttribute == null) {
+            sessionAttribute = Session.class.getName();
+        }
+
+        nodeAttribute = config.getInitParameter(Node.class.getName());
+        if (nodeAttribute == null) {
+            nodeAttribute = Node.class.getName();
+        }
+    }
+
+    public void destroy() {
+    }
+
+    public void doFilter(
+            ServletRequest request, ServletResponse response,
+            FilterChain chain) throws IOException, ServletException {
+        HttpServletRequest httpRequest = (HttpServletRequest) request;
+        HttpServletResponse httpResponse = (HttpServletResponse) response;
+        try {
+            Credentials credentials = getCredentials(httpRequest);
+            Session session = repository.login(credentials, workspace);
+            try {
+                request.setAttribute(sessionAttribute, session);
+                request.setAttribute(nodeAttribute, session.getRootNode());
+                chain.doFilter(request, response);
+                if (session.hasPendingChanges()) {
+                    session.save();
+                }
+            } finally {
+                session.logout();
+            }
+        } catch (ServletException e) {
+            Throwable cause = e.getRootCause();
+            if (cause instanceof AccessDeniedException) {
+                httpResponse.sendError(
+                        HttpServletResponse.SC_FORBIDDEN, cause.getMessage());
+            } else {
+                throw e;
+            }
+        } catch (LoginException e) {
+            httpResponse.sendError(
+                    HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
+        } catch (NoSuchWorkspaceException e) {
+            throw new ServletException(
+                    "Workspace " + workspace
+                    + " not found in the content repository", e);
+        } catch (RepositoryException e) {
+            throw new ServletException(
+                    "Unable to access the content repository", e);
+        }
+    }
+
+    protected abstract Credentials getCredentials(HttpServletRequest request);
+
+}
diff --git a/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/login/BasicLoginFilter.java b/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/login/BasicLoginFilter.java
index 6e9b10b..d4490d4 100644
--- a/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/login/BasicLoginFilter.java
+++ b/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/login/BasicLoginFilter.java
@@ -1,38 +1,38 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.servlet.login;
-
-import javax.jcr.Credentials;
-import javax.jcr.SimpleCredentials;
-import javax.servlet.http.HttpServletRequest;
-
-/**
- *
- * @since Apache Jackrabbit 1.6
- */
-public class BasicLoginFilter extends AbstractLoginFilter {
-
-    protected Credentials getCredentials(HttpServletRequest request) {
-        String authorization = request.getHeader("Authorization");
-        if (authorization != null) {
-            return new SimpleCredentials("TODO", "TODO".toCharArray());
-        } else {
-            return null;
-        }
-    }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.servlet.login;
+
+import javax.jcr.Credentials;
+import javax.jcr.SimpleCredentials;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ *
+ * @since Apache Jackrabbit 1.6
+ */
+public class BasicLoginFilter extends AbstractLoginFilter {
+
+    protected Credentials getCredentials(HttpServletRequest request) {
+        String authorization = request.getHeader("Authorization");
+        if (authorization != null) {
+            return new SimpleCredentials("TODO", "TODO".toCharArray());
+        } else {
+            return null;
+        }
+    }
+
+}
diff --git a/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/login/ContainerLoginFilter.java b/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/login/ContainerLoginFilter.java
index a0cb235..b61bbe0 100644
--- a/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/login/ContainerLoginFilter.java
+++ b/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/login/ContainerLoginFilter.java
@@ -1,64 +1,64 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.servlet.login;
-
-import javax.jcr.Credentials;
-import javax.jcr.SimpleCredentials;
-import javax.servlet.FilterConfig;
-import javax.servlet.http.HttpServletRequest;
-
-/**
- * Login filter that relies on container authentication to provide the
- * authenticated username of a request. This username is associated with
- * a dummy password (empty by default, configurable through the init
- * parameter "password") in a {@link SimpleCredentials} object that is
- * used to log in to the underlying content repository. If no authenticated
- * user is found, then <code>null</code> credentials are used.
- * <p>
- * It is expected that the underlying repository is configured to simply
- * trust the given username. If the same repository is also made available
- * for direct logins, then a special secret password that allows logins with
- * any username could be configured just for this filter.
- *
- * @since Apache Jackrabbit 1.6
- */
-public class ContainerLoginFilter extends AbstractLoginFilter {
-
-    /**
-     * The dummy password used for the repository login. Empty by default.
-     */
-    private char[] password = new char[0];
-
-    public void init(FilterConfig config) {
-        super.init(config);
-
-        String password = config.getInitParameter("password");
-        if (password != null) {
-            this.password = password.toCharArray();
-        }
-    }
-
-    protected Credentials getCredentials(HttpServletRequest request) {
-        String user = request.getRemoteUser();
-        if (user != null) {
-            return new SimpleCredentials(user, password);
-        } else {
-            return null;
-        }
-    }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.servlet.login;
+
+import javax.jcr.Credentials;
+import javax.jcr.SimpleCredentials;
+import javax.servlet.FilterConfig;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Login filter that relies on container authentication to provide the
+ * authenticated username of a request. This username is associated with
+ * a dummy password (empty by default, configurable through the init
+ * parameter "password") in a {@link SimpleCredentials} object that is
+ * used to log in to the underlying content repository. If no authenticated
+ * user is found, then <code>null</code> credentials are used.
+ * <p>
+ * It is expected that the underlying repository is configured to simply
+ * trust the given username. If the same repository is also made available
+ * for direct logins, then a special secret password that allows logins with
+ * any username could be configured just for this filter.
+ *
+ * @since Apache Jackrabbit 1.6
+ */
+public class ContainerLoginFilter extends AbstractLoginFilter {
+
+    /**
+     * The dummy password used for the repository login. Empty by default.
+     */
+    private char[] password = new char[0];
+
+    public void init(FilterConfig config) {
+        super.init(config);
+
+        String password = config.getInitParameter("password");
+        if (password != null) {
+            this.password = password.toCharArray();
+        }
+    }
+
+    protected Credentials getCredentials(HttpServletRequest request) {
+        String user = request.getRemoteUser();
+        if (user != null) {
+            return new SimpleCredentials(user, password);
+        } else {
+            return null;
+        }
+    }
+
+}
diff --git a/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/login/NullLoginFilter.java b/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/login/NullLoginFilter.java
index 37a69a2..14f29fa 100644
--- a/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/login/NullLoginFilter.java
+++ b/jackrabbit-jcr-servlet/src/main/java/org/apache/jackrabbit/servlet/login/NullLoginFilter.java
@@ -1,43 +1,43 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.servlet.login;
-
-import javax.jcr.Credentials;
-import javax.servlet.http.HttpServletRequest;
-
-/**
- * Login filter that always uses <code>null</code> credentials for logging in
- * to the content repository. This is useful for example for public web sites
- * where all repository access is performed using anonymous sessions. Another
- * use case for this login filter is when login information is made available
- * to the content repository through JAAS or some other out-of-band mechanism.
- *
- * @since Apache Jackrabbit 1.6
- */
-public class NullLoginFilter extends AbstractLoginFilter {
-
-    /**
-     * Always returns <code>null</code>.
-     *
-     * @param request ignored
-     * @return <code>null</code> credentials
-     */
-    protected Credentials getCredentials(HttpServletRequest request) {
-        return null;
-    }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.servlet.login;
+
+import javax.jcr.Credentials;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Login filter that always uses <code>null</code> credentials for logging in
+ * to the content repository. This is useful for example for public web sites
+ * where all repository access is performed using anonymous sessions. Another
+ * use case for this login filter is when login information is made available
+ * to the content repository through JAAS or some other out-of-band mechanism.
+ *
+ * @since Apache Jackrabbit 1.6
+ */
+public class NullLoginFilter extends AbstractLoginFilter {
+
+    /**
+     * Always returns <code>null</code>.
+     *
+     * @param request ignored
+     * @return <code>null</code> credentials
+     */
+    protected Credentials getCredentials(HttpServletRequest request) {
+        return null;
+    }
+
+}
diff --git a/jackrabbit-jcr-tests/pom.xml b/jackrabbit-jcr-tests/pom.xml
index 72c5ca0..f114596 100644
--- a/jackrabbit-jcr-tests/pom.xml
+++ b/jackrabbit-jcr-tests/pom.xml
@@ -26,7 +26,7 @@
   <parent>
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>jackrabbit-parent</artifactId>
-    <version>2.3.6</version>
+    <version>2.10.1</version>
     <relativePath>../jackrabbit-parent/pom.xml</relativePath>
   </parent>
   <artifactId>jackrabbit-jcr-tests</artifactId>
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/AbstractJCRTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/AbstractJCRTest.java
index 1e69ce2..ce0c181 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/AbstractJCRTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/AbstractJCRTest.java
@@ -36,7 +36,6 @@ import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.nodetype.NodeType;
 import javax.jcr.nodetype.PropertyDefinition;
 import javax.jcr.retention.RetentionManager;
-import javax.jcr.retention.RetentionPolicy;
 
 import java.util.StringTokenizer;
 import java.util.Random;
@@ -51,7 +50,7 @@ public abstract class AbstractJCRTest extends JUnitTest {
     /**
      * Pool of helper objects to access repository transparently
      */
-    private static final RepositoryHelperPool HELPER_POOL = new RepositoryHelperPoolImpl();
+    private static final RepositoryHelperPool HELPER_POOL = RepositoryHelperPoolImpl.getInstance();
 
     /**
      * Namespace URI for jcr prefix.
@@ -239,6 +238,11 @@ public abstract class AbstractJCRTest extends JUnitTest {
     protected String testNodeType;
 
     /**
+     * The node type name for the test root node.
+     */
+    protected String testNodeTypeTestRoot;
+
+    /**
      * A node type that does not allow any child nodes, such as nt:base.
      */
     protected String testNodeTypeNoChildren;
@@ -309,6 +313,10 @@ public abstract class AbstractJCRTest extends JUnitTest {
         // cut off '/' to build testPath
         testPath = testRoot.substring(1);
         testNodeType = getProperty(RepositoryStub.PROP_NODETYPE);
+        testNodeTypeTestRoot = getProperty(RepositoryStub.PROP_NODETYPETESTROOT);
+        if (testNodeTypeTestRoot == null) {
+            testNodeTypeTestRoot = testNodeType; // backwards compatibility
+        }
         testNodeTypeNoChildren = getProperty(RepositoryStub.PROP_NODETYPENOCHILDREN);
         // setup node names
         nodeName1 = getProperty(RepositoryStub.PROP_NODE_NAME1);
@@ -589,21 +597,54 @@ public abstract class AbstractJCRTest extends JUnitTest {
     }
 
     /**
-     * Returns the local name for the given <code>name</code>.
-     *
-     * @param name the name.
+     * Returns the local name for the given <code>jcrName</code>.
+     * 
+     * @param jcrName
+     *            the name.
      * @return the local name part.
      */
-    protected static String getLocalName(String name) {
-        int idx = name.indexOf(':');
+    protected static String getLocalName(String jcrName) {
+        int idx = jcrName.indexOf(':');
+        if (idx != -1) {
+            return jcrName.substring(idx + 1);
+        } else {
+            return jcrName;
+        }
+    }
+
+    /**
+     * Returns the prefix for the given <code>jcrName</code>.
+     * 
+     * @param jcrName
+     *            the name.
+     * @return the prefix part (empty string when not prefixed)
+     */
+    protected static String getPrefix(String jcrName) {
+        int idx = jcrName.indexOf(':');
         if (idx != -1) {
-            return name.substring(idx + 1);
+            return jcrName.substring(0, idx);
         } else {
-            return name;
+            return "";
         }
     }
 
     /**
+     * Returns the expanded name for the given <code>jcrName</code>.
+     * 
+     * @param jcrName
+     *            the name.
+     * @return the expanded name representation
+     * @throws RepositoryException
+     * @throws NamespaceException
+     */
+    protected static String getQualifiedName(Session session, String jcrName) throws RepositoryException {
+        String prefix = getPrefix(jcrName);
+        String namespace = session.getNamespaceURI(prefix);
+        String localname = getLocalName(jcrName);
+        return (namespace.length() > 0 ? "{" + namespace + "}" : "{}") + localname;
+    }
+
+    /**
      * Returns the name of a workspace that is not accessible from
      * <code>session</code>.
      * @param session the session.
@@ -807,29 +848,28 @@ public abstract class AbstractJCRTest extends JUnitTest {
         s.refresh(false);
         Node root = s.getRootNode();
         Node testRootNode;
-        
-        RetentionManager rm;
-        try {
-            rm = s.getRetentionManager();
-        } catch (UnsupportedRepositoryOperationException ex) {
-            rm = null;
-        }
-        
+
         if (root.hasNode(testPath)) {
+            RetentionManager rm;
+            try {
+                rm = s.getRetentionManager();
+            } catch (UnsupportedRepositoryOperationException e) {
+                rm = null;
+            }
+
             // clean test root
             testRootNode = root.getNode(testPath);
-            for (NodeIterator children = testRootNode.getNodes(); children.hasNext();) {
+            NodeIterator children = testRootNode.getNodes();
+            while (children.hasNext()) {
                 Node child = children.nextNode();
 
                 // Remove retention policy if needed
-                if (rm != null) {
-                    RetentionPolicy pol = rm.getRetentionPolicy(child.getPath());
-                    if (pol != null) {
-                        rm.removeRetentionPolicy(child.getPath());
-                        s.save();
-                    }
+                String childPath = child.getPath();
+                if (rm != null && rm.getRetentionPolicy(childPath) != null) {
+                    rm.removeRetentionPolicy(childPath);
+                    s.save();
                 }
-                
+
                 NodeDefinition nodeDef = child.getDefinition();
                 if (!nodeDef.isMandatory() && !nodeDef.isProtected()) {
                     // try to remove child
@@ -849,7 +889,7 @@ public abstract class AbstractJCRTest extends JUnitTest {
                 if (currentNode.hasNode(name)) {
                     currentNode = currentNode.getNode(name);
                 } else {
-                    currentNode = currentNode.addNode(name, testNodeType);
+                    currentNode = currentNode.addNode(name, testNodeTypeTestRoot);
                 }
             }
             testRootNode = currentNode;
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/ISO8601.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/ISO8601.java
index 775b3f8..430cdb4 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/ISO8601.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/ISO8601.java
@@ -24,7 +24,7 @@ import java.util.TimeZone;
  * The <code>ISO8601</code> utility class provides helper methods
  * to deal with date/time formatting using a specific ISO8601-compliant
  * format (see <a href="http://www.w3.org/TR/NOTE-datetime">ISO 8601</a>).
- * <p/>
+ * <p>
  * The currently supported format is:
  * <pre>
  *   ±YYYY-MM-DDThh:mm:ss.SSSTZD
@@ -290,7 +290,7 @@ public final class ISO8601 {
 
     /**
      * Appends a zero-padded number to the given string buffer.
-     * <p/>
+     * <p>
      * This is an internal helper method which doesn't perform any
      * validation on the given arguments.
      *
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/JNDIRepositoryStub.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/JNDIRepositoryStub.java
index 6e81053..d2041f7 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/JNDIRepositoryStub.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/JNDIRepositoryStub.java
@@ -59,9 +59,10 @@ public class JNDIRepositoryStub extends RepositoryStub {
 
             } catch (ClassCastException e) {
                 // ClassCastException may be thrown by ProtableRemoteObject.narrow()
-                throw new RepositoryStubException("Object cannot be narrowed to javax.jcr.Repository: " + e);
+                throw new RepositoryStubException(
+                        "Object cannot be narrowed to javax.jcr.Repository", e);
             } catch (NamingException e) {
-                throw new RepositoryStubException(e.getMessage());
+                throw new RepositoryStubException(e);
             }
         }
         return repository;
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/RepositoryHelperPool.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/RepositoryHelperPool.java
index 5b4c5ea..868a745 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/RepositoryHelperPool.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/RepositoryHelperPool.java
@@ -31,6 +31,16 @@ public interface RepositoryHelperPool {
     public RepositoryHelper borrowHelper() throws InterruptedException;
 
     /**
+     * Borrows all available repository helper instances. Waits until one
+     * becomes available.
+     *
+     * @return a repository helper.
+     * @throws InterruptedException if this thread is interrupted while waiting
+     *                              for a repository helper.
+     */
+    public RepositoryHelper[] borrowHelpers() throws InterruptedException;
+
+    /**
      * Returns the given repository helper to the pool.
      *
      * @param helper the repository helper to return.
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/RepositoryHelperPoolImpl.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/RepositoryHelperPoolImpl.java
index 594da2d..bbdc443 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/RepositoryHelperPoolImpl.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/RepositoryHelperPoolImpl.java
@@ -21,7 +21,6 @@ import java.util.LinkedList;
 import java.util.Properties;
 import java.util.Map;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.io.InputStream;
 import java.io.IOException;
 
@@ -33,9 +32,18 @@ public class RepositoryHelperPoolImpl implements RepositoryHelperPool {
 
     private static final String PROP_FILE = "repositoryHelperPool.properties";
 
-    private List helpers = new LinkedList();
+    private List<RepositoryHelper> helpers = new LinkedList<RepositoryHelper>();
 
-    public RepositoryHelperPoolImpl() {
+    private static RepositoryHelperPool POOL = null;
+
+    public synchronized static RepositoryHelperPool getInstance() {
+        if (POOL == null) {
+            POOL = new RepositoryHelperPoolImpl();
+        }
+        return POOL;
+    }
+
+    private RepositoryHelperPoolImpl() {
         InputStream in = RepositoryHelperPoolImpl.class.getClassLoader().getResourceAsStream(PROP_FILE);
         if (in != null) {
             try {
@@ -43,9 +51,8 @@ public class RepositoryHelperPoolImpl implements RepositoryHelperPool {
                 props.load(in);
                 for (int i = 0;; i++) {
                     String prefix = "helper." + i + ".";
-                    Map helperProp = new HashMap();
-                    for (Iterator it = props.entrySet().iterator(); it.hasNext(); ) {
-                        Map.Entry entry = (Map.Entry) it.next();
+                    Map<String, Object> helperProp = new HashMap<String, Object>();
+                    for (Map.Entry<Object, Object> entry : props.entrySet()) {
                         String key = (String) entry.getKey();
                         if (key.startsWith(prefix)) {
                             helperProp.put(key.substring(prefix.length()), entry.getValue());
@@ -81,7 +88,18 @@ public class RepositoryHelperPoolImpl implements RepositoryHelperPool {
         while (helpers.isEmpty()) {
             wait();
         }
-        return (RepositoryHelper) helpers.remove(0);
+        return helpers.remove(0);
+    }
+
+    public synchronized RepositoryHelper[] borrowHelpers() throws InterruptedException {
+        while (helpers.isEmpty()) {
+            wait();
+        }
+        try {
+            return helpers.toArray(new RepositoryHelper[helpers.size()]);
+        } finally {
+            helpers.clear();
+        }
     }
 
     public synchronized void returnHelper(RepositoryHelper helper) {
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/RepositoryStub.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/RepositoryStub.java
index 9d35759..a57c29e 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/RepositoryStub.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/RepositoryStub.java
@@ -21,7 +21,6 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
 import java.security.Principal;
 import java.util.Map;
 import java.util.Properties;
@@ -63,6 +62,8 @@ public abstract class RepositoryStub {
 
     public static final String PROP_NODETYPE = "nodetype";
 
+    public static final String PROP_NODETYPETESTROOT = "nodetypetestroot";
+
     public static final String PROP_NODETYPENOCHILDREN = "nodetypenochildren";
 
     public static final String PROP_TESTROOT = "testroot";
@@ -182,8 +183,8 @@ public abstract class RepositoryStub {
                 try {
                     props.load(new FileInputStream(implPropFile));
                 } catch (IOException e) {
-                    throw new RepositoryStubException("Unable to load config file: "
-                            + implProp + " " + e.toString());
+                    throw new RepositoryStubException(
+                            "Unable to load config file: " + implProp, e);
                 }
             } else {
                 throw new RepositoryStubException("File does not exist: " + implProp);
@@ -197,8 +198,8 @@ public abstract class RepositoryStub {
                 try {
                     props.load(is);
                 } catch (IOException e) {
-                    throw new RepositoryStubException("Exception reading "
-                            + STUB_IMPL_PROPS + ": " + e.toString());
+                    throw new RepositoryStubException(
+                            "Exception reading " + STUB_IMPL_PROPS, e);
                 }
             }
         }
@@ -214,18 +215,8 @@ public abstract class RepositoryStub {
             Class stubClass = Class.forName(className);
             Constructor constr = stubClass.getConstructor(new Class[]{Properties.class});
             stub = (RepositoryStub) constr.newInstance(new Object[]{props});
-        } catch (ClassCastException e) {
-            throw new RepositoryStubException(e.toString());
-        } catch (NoSuchMethodException e) {
-            throw new RepositoryStubException(e.toString());
-        } catch (ClassNotFoundException e) {
-            throw new RepositoryStubException(e.toString());
-        } catch (InstantiationException e) {
-            throw new RepositoryStubException(e.toString());
-        } catch (IllegalAccessException e) {
-            throw new RepositoryStubException(e.toString());
-        } catch (InvocationTargetException e) {
-            throw new RepositoryStubException(e.toString());
+        } catch (Exception e) {
+            throw new RepositoryStubException(e);
         }
 
         return stub;
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/RepositoryStubException.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/RepositoryStubException.java
index dbe2fc3..35fab06 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/RepositoryStubException.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/RepositoryStubException.java
@@ -24,4 +24,13 @@ public class RepositoryStubException extends Exception {
     public RepositoryStubException(String msg) {
         super(msg);
     }
+
+    public RepositoryStubException(Throwable cause) {
+        super(cause);
+    }
+
+    public RepositoryStubException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
 }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/AbstractImportXmlTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/AbstractImportXmlTest.java
index b2eadee..50a15b2 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/AbstractImportXmlTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/AbstractImportXmlTest.java
@@ -96,7 +96,7 @@ abstract class AbstractImportXmlTest extends AbstractJCRTest {
 
     // names for namespace import
     protected final String TEST_PREFIX = "docview";
-    protected final String TEST_URI = "www.apache.org/jackrabbit/test/namespaceImportTest";
+    protected final String TEST_URI = "http://www.apache.org/jackrabbit/test/namespaceImportTest";
     protected final String XML_NS = "xmlns";
 
     // xml document related names
@@ -268,22 +268,27 @@ abstract class AbstractImportXmlTest extends AbstractJCRTest {
         serialize(document);
         BufferedInputStream bin = new BufferedInputStream(new FileInputStream(file));
 
-        ContentHandler handler;
-        if (withWorkspace) {
-            handler = workspace.getImportContentHandler(absPath, uuidBehaviour);
-        } else {
-            handler = session.getImportContentHandler(absPath, uuidBehaviour);
-        }
-
-        XMLReader reader = XMLReaderFactory.createXMLReader();
-        reader.setFeature("http://xml.org/sax/features/namespaces", true);
-        reader.setFeature("http://xml.org/sax/features/namespace-prefixes", false);
+        try {
+            ContentHandler handler;
+            if (withWorkspace) {
+                handler = workspace.getImportContentHandler(absPath, uuidBehaviour);
+            } else {
+                handler = session.getImportContentHandler(absPath, uuidBehaviour);
+            }
 
-        reader.setContentHandler(handler);
-        reader.parse(new InputSource(bin));
+            XMLReader reader = XMLReaderFactory.createXMLReader();
+            reader.setFeature("http://xml.org/sax/features/namespaces", true);
+            reader.setFeature("http://xml.org/sax/features/namespace-prefixes", false);
+            
+            reader.setContentHandler(handler);
+            reader.parse(new InputSource(bin));
 
-        if (!withWorkspace) {
-            session.save();
+            if (!withWorkspace) {
+                session.save();
+            }
+        }
+        finally {
+            bin.close();
         }
     }
 
@@ -358,7 +363,7 @@ abstract class AbstractImportXmlTest extends AbstractJCRTest {
         }
         n1.addMixin(mixReferenceable);
         // make sure jcr:uuid is available
-        testRootNode.save();
+        testRootNode.getSession().save();
         return n1.getUUID();
     }
 
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/AbstractWorkspaceCopyTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/AbstractWorkspaceCopyTest.java
index 549588a..22622e3 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/AbstractWorkspaceCopyTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/AbstractWorkspaceCopyTest.java
@@ -73,7 +73,7 @@ abstract class AbstractWorkspaceCopyTest extends AbstractJCRTest {
         // create a non-referenceable node
         try {
             node2 = testRootNode.addNode(nodeName2, testNodeType);
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (RepositoryException e) {
             fail("Failed to createtest node." + e.getMessage());
         }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/AddNodeTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/AddNodeTest.java
index db9d95c..18439f1 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/AddNodeTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/AddNodeTest.java
@@ -16,6 +16,8 @@
  */
 package org.apache.jackrabbit.test.api;
 
+import java.text.Normalizer;
+
 import org.apache.jackrabbit.test.AbstractJCRTest;
 import org.apache.jackrabbit.test.NotExecutableException;
 
@@ -46,7 +48,7 @@ public class AddNodeTest extends AbstractJCRTest {
      */
     public void testName() throws RepositoryException {
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Wrong node name.", n1.getName(), nodeName1);
     }
 
@@ -55,7 +57,7 @@ public class AddNodeTest extends AbstractJCRTest {
      */
     public void testNodeType() throws RepositoryException {
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         String ntName = n1.getPrimaryNodeType().getName();
         assertEquals("Wrong node NodeType name.", testNodeType, ntName);
     }
@@ -68,7 +70,7 @@ public class AddNodeTest extends AbstractJCRTest {
         if (testRootNode.getDefinition().allowsSameNameSiblings()) {
             Node n1 = testRootNode.addNode(nodeName1, testNodeType);
             Node n2 = testRootNode.addNode(nodeName1, testNodeType);
-            testRootNode.save();
+            testRootNode.getSession().save();
             assertEquals("Names of same name siblings are not equal.",
                     n1.getName(), n2.getName());
         } else {
@@ -143,7 +145,7 @@ public class AddNodeTest extends AbstractJCRTest {
      */
     public void testPath() throws RepositoryException {
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         String expected = testRootNode.getPath() + "/" + nodeName1;
         assertEquals("Wrong path for created node.", expected, n1.getPath());
     }
@@ -264,4 +266,61 @@ public class AddNodeTest extends AbstractJCRTest {
             // ok, works as expected.
         }
     }
+
+    /**
+     * Tests the behavior with respect to case-sensitivity
+     */
+    public void testSimilarNodeNamesUpperLower() throws RepositoryException {
+
+        internalTestSimilarNodeNames("Test-a", "Test-A");
+    }
+
+    /**
+     * Tests the behavior with respect to Unicode normalization
+     */
+    public void testSimilarNodeNamesNfcNfd() throws RepositoryException {
+
+        String precomposed = "Test-\u00e4"; // a umlaut
+        String decomposed = Normalizer.normalize(precomposed, Normalizer.Form.NFD);
+        assertFalse(precomposed.equals(decomposed)); // sanity check
+        internalTestSimilarNodeNames(precomposed, decomposed);
+    }
+
+    /**
+     * Tests behavior for creation of "similarly" named nodes
+     * @throws RepositoryException 
+     */
+    private void internalTestSimilarNodeNames(String name1, String name2) throws RepositoryException {
+
+        Node n1 = null, n2 = null;
+        Session s = testRootNode.getSession();
+
+        try {
+            n1 = testRootNode.addNode(name1);
+            assertEquals(name1, n1.getName());
+            s.save();
+
+            assertFalse(testRootNode.hasNode(name2));
+        } catch (ConstraintViolationException e) {
+            // accepted
+        }
+        try {
+            n2 = testRootNode.addNode(name2);
+            assertEquals(name2, n2.getName());
+            s.save();
+        } catch (ConstraintViolationException e) {
+            // accepted
+        }
+
+        // If both nodes have been created, do further checks
+        if (n1 != null && n2 != null) {
+            assertFalse(n1.isSame(n2));
+            assertFalse(n1.getIdentifier().equals(n2.getIdentifier()));
+            String n2path = n2.getPath();
+            n1.remove();
+            s.save();
+            Node n3 = s.getNode(n2path);
+            assertTrue(n3.isSame(n2));
+        }
+    }
 }
\ No newline at end of file
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/BinaryPropertyTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/BinaryPropertyTest.java
index c96cb5c..8c14edc 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/BinaryPropertyTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/BinaryPropertyTest.java
@@ -395,7 +395,7 @@ public class BinaryPropertyTest extends AbstractPropertyTest {
         } finally {
             binary.dispose();
         }
-        long bytes = PropertyUtil.countBytes(prop.getValue());
+        long bytes = PropertyUtil.countBytes(val);
         if (bytes != -1) {
             assertEquals("Binary.getSize() returns wrong number of bytes.",
                     bytes, length);
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ExportDocViewTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ExportDocViewTest.java
index 8b43292..a9ef57e 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ExportDocViewTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ExportDocViewTest.java
@@ -1119,13 +1119,11 @@ public class ExportDocViewTest extends AbstractJCRTest {
         private static final String xmlnsURI = "http://www.w3.org/2000/xmlns/";
         private static final String xmlnsPrefix = "xmlns";
 
-        Element elem;
         NamedNodeMap attrs;
         Map<String, String> nsAttrs;
         Map<String, String> nonNsAttrs;
 
         AttributeSeparator(Element elem) {
-            this.elem = elem;
             nsAttrs = new HashMap<String, String>();
             nonNsAttrs = new HashMap<String, String>();
             attrs = elem.getAttributes();
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NamespaceRegistryTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NamespaceRegistryTest.java
index 3ce5033..8bcc1e1 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NamespaceRegistryTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NamespaceRegistryTest.java
@@ -142,18 +142,18 @@ public class NamespaceRegistryTest extends AbstractJCRTest {
 
         try {
             created = testRootNode.addNode(namespacePrefix + ":root");
-            testRootNode.save();
+            testRootNode.getSession().save();
         }
         catch (RepositoryException ex) {
             // that didn't work; maybe the repository allows a property here?
             testRootNode.getSession().refresh(false);
             created = testRootNode.setProperty(namespacePrefix + ":root", "test");
-            testRootNode.save();
+            testRootNode.getSession().save();
         }
 
         // Need to remove it here, otherwise teardown can't unregister the NS.
         testRootNode.getSession().getItem(created.getPath()).remove();
-        testRootNode.save();
+        testRootNode.getSession().save();
     }
 
     /**
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeAddMixinTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeAddMixinTest.java
index f76b98a..28570a1 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeAddMixinTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeAddMixinTest.java
@@ -70,7 +70,7 @@ public class NodeAddMixinTest extends AbstractJCRTest {
 
         // it is implementation-specific if a added mixin is available
         // before or after save therefore save before further tests
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // test if added mixin is available by node.getMixinNodeTypes()
         NodeType mixins[] = node.getMixinNodeTypes();
@@ -180,7 +180,7 @@ public class NodeAddMixinTest extends AbstractJCRTest {
         Node node = testRootNode.addNode(nodeName1, testNodeType);
         // or try to make it lockable if it is not
         ensureMixinType(node, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         String mixinName = NodeMixinUtil.getAddableMixinName(session, node);
         if (mixinName == null) {
@@ -235,7 +235,7 @@ public class NodeAddMixinTest extends AbstractJCRTest {
         Node node = testRootNode.addNode(nodeName1, testNodeType);
         // or try to make it versionable if it is not
         ensureMixinType(node, mixVersionable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         String mixinName = NodeMixinUtil.getAddableMixinName(session, node);
         if (mixinName == null || node.isNodeType(mixinName)) {
@@ -267,7 +267,7 @@ public class NodeAddMixinTest extends AbstractJCRTest {
         Node node = testRootNode.addNode(nodeName1, testNodeType);
         ensureMixinType(node, mixReferenceable);
         // implementation specific: mixin may take effect only upon save
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // check that it did
         assertTrue(node.isNodeType(mixReferenceable));
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeCanAddMixinTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeCanAddMixinTest.java
index 9d644d6..21b7364 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeCanAddMixinTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeCanAddMixinTest.java
@@ -55,7 +55,7 @@ public class NodeCanAddMixinTest extends AbstractJCRTest {
         Node node = testRootNode.addNode(nodeName1, testNodeType);
         // or try to make it lockable if it is not
         ensureMixinType(node, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         String mixinName = NodeMixinUtil.getAddableMixinName(session, node);
         if (mixinName == null) {
@@ -99,7 +99,7 @@ public class NodeCanAddMixinTest extends AbstractJCRTest {
         Node node = testRootNode.addNode(nodeName1, testNodeType);
         // or try to make it versionable if it is not
         ensureMixinType(node, mixVersionable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         String mixinName = NodeMixinUtil.getAddableMixinName(session, node);
         if (mixinName == null) {
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeDiscoveringNodeTypesTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeDiscoveringNodeTypesTest.java
index ff1e2bb..86e19c6 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeDiscoveringNodeTypesTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeDiscoveringNodeTypesTest.java
@@ -66,7 +66,7 @@ public class NodeDiscoveringNodeTypesTest extends AbstractJCRTest {
     }
 
     /**
-     * Releases the session aquired in {@link #setUp()}.
+     * Releases the session acquired in {@link #setUp()}.
      */
     protected void tearDown() throws Exception {
         if (session != null) {
@@ -103,7 +103,7 @@ public class NodeDiscoveringNodeTypesTest extends AbstractJCRTest {
 
     /**
      * Test if getMixinNodeType returns the node types according to the property
-     * "jcr:mixinTypes". Therefor a node with mixin types is located recursively
+     * "jcr:mixinTypes". Therefore a node with mixin types is located recursively
      * in the entire repository. A NotExecutableException is thrown when no such
      * node is found.
      */
@@ -165,22 +165,29 @@ public class NodeDiscoveringNodeTypesTest extends AbstractJCRTest {
                 "nodeTypeName is the name of the primary node type",
                 testRootNode.isNodeType(nodeTypeName));
 
+        String expNodeTypeName = getQualifiedName(testRootNode.getSession(), nodeTypeName);
+        assertTrue("isNodeType(String expNodeTypeName) must return true if "
+                + "expNodeTypeName is the name of the primary node type", testRootNode.isNodeType(expNodeTypeName));
+
         // test with mixin node's name
         // (if such a node is available)
         Node nodeWithMixin = locateNodeWithMixinNodeTypes(testRootNode);
         if (nodeWithMixin != null) {
             NodeType types[] = nodeWithMixin.getMixinNodeTypes();
             nodeTypeName = types[0].getName();
-            assertTrue("isNodeType(String nodeTypeName) must return true if " +
-                    "nodeTypeName is the name of one of the " +
-                    "mixin node types",
-                    nodeWithMixin.isNodeType(nodeTypeName));
+            expNodeTypeName = getQualifiedName(testRootNode.getSession(), nodeTypeName);
+            assertTrue("isNodeType(String nodeTypeName) must return true if " + "nodeTypeName is the name of one of the "
+                    + "mixin node types", nodeWithMixin.isNodeType(nodeTypeName));
+            assertTrue("isNodeType(String expNodeTypeName) must return true if " + "expNodeTypeName is the name of one of the "
+                    + "mixin node types", nodeWithMixin.isNodeType(expNodeTypeName));
         }
 
         // test with the name of predefined supertype "nt:base"
         assertTrue("isNodeType(String nodeTypeName) must return true if " +
                 "nodeTypeName is the name of a node type of a supertype",
                 testRootNode.isNodeType(ntBase));
+        assertTrue("isNodeType(String nodeTypeName) must return true if "
+                + "nodeTypeName is the name of a node type of a supertype", testRootNode.isNodeType(NodeType.NT_BASE));
     }
 
     //-----------------------< internal >---------------------------------------
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeItemIsModifiedTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeItemIsModifiedTest.java
index 32054da..868634b 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeItemIsModifiedTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeItemIsModifiedTest.java
@@ -63,7 +63,7 @@ public class NodeItemIsModifiedTest extends AbstractJCRTest {
      */
     public void testPersistentNewNodeItemIsModified () throws RepositoryException {
         Node testNode = testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Item testNodeItem = superuser.getItem(testNode.getPath());
         // check testNodeItem.isModified() for a new NodeItem after save
         assertFalse("Item.isModified() must return false after a new NodeItem is added and the parent Node is saved", testNodeItem.isModified());
@@ -78,7 +78,7 @@ public class NodeItemIsModifiedTest extends AbstractJCRTest {
      */
     public void testTransientNodeItemIsModified () throws RepositoryException {
         Node testNode = testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         // modify the persistent testNode
         testNode.setProperty(propertyName1, "test");
         Item testNodeItem = superuser.getItem(testNode.getPath());
@@ -95,7 +95,7 @@ public class NodeItemIsModifiedTest extends AbstractJCRTest {
      */
     public void testPersistentNodeItemIsModified () throws RepositoryException {
         Node testNode = testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         // modify the persistent testNode
         testNode.setProperty(propertyName1, "test");
         testNode.save();
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeItemIsNewTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeItemIsNewTest.java
index a221a8f..8e8b8f0 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeItemIsNewTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeItemIsNewTest.java
@@ -58,7 +58,7 @@ public class NodeItemIsNewTest extends AbstractJCRTest {
      */
     public void testPersistentNodeItemIsNew () throws RepositoryException {
         Node testNode = testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Item testNodeItem = superuser.getItem(testNode.getPath());
         // check testNodeItem is new after save
         assertFalse("Item.isNew() must return false after a new NodeItem is added and the parent Node is saved", testNodeItem.isNew());
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeMixinUtil.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeMixinUtil.java
index 9caef5d..34e3f3a 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeMixinUtil.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeMixinUtil.java
@@ -16,8 +16,8 @@
  */
 package org.apache.jackrabbit.test.api;
 
-import java.util.Arrays;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
 
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
@@ -48,7 +48,9 @@ public class NodeMixinUtil {
 
         while (mixins.hasNext()) {
             String name = mixins.nextNodeType().getName();
-            if (node.canAddMixin(name) && !mixShareable.equals(name)) {
+            if (node.canAddMixin(name)
+                    && !node.isNodeType(name)
+                    && !mixShareable.equals(name)) {
                 return name;
             }
         }
@@ -58,12 +60,16 @@ public class NodeMixinUtil {
     public static String getNotAssignedMixinName(Session session, Node node) throws RepositoryException {
         NodeTypeManager manager = session.getWorkspace().getNodeTypeManager();
         NodeTypeIterator mixins = manager.getMixinNodeTypes();
-        List<NodeType> existingMixins = Arrays.asList(node.getMixinNodeTypes());
+
+        Set<String> existingMixins = new HashSet<String>();
+        for (NodeType nt : node.getMixinNodeTypes()) {
+            existingMixins.add(nt.getName());
+        }
 
         while (mixins.hasNext()) {
-            NodeType nt = mixins.nextNodeType();
-            if (!existingMixins.contains(nt)) {
-                return nt.getName();
+            String ntName = mixins.nextNodeType().getName();
+            if (!existingMixins.contains(ntName)) {
+                return ntName;
             }
         }
         return null;
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeRemoveMixinTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeRemoveMixinTest.java
index 7d2f7cc..ea3cbec 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeRemoveMixinTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeRemoveMixinTest.java
@@ -57,7 +57,7 @@ public class NodeRemoveMixinTest extends AbstractJCRTest {
         }
 
         node.addMixin(mixinName);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         try {
             node.removeMixin(mixinName);
@@ -86,7 +86,7 @@ public class NodeRemoveMixinTest extends AbstractJCRTest {
 
         // it is implementation-specific if a removed mixin isn't available
         // before or after save therefore save before further tests
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // test if removed mixin isn't available anymore by node.getMixinNodeTypes()
         assertTrue("removeMixin(String mixinName) did not remove mixin.",
@@ -110,7 +110,7 @@ public class NodeRemoveMixinTest extends AbstractJCRTest {
         }
 
         node.addMixin(mixinName);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
 
         String notAssignedMixin = NodeMixinUtil.getNotAssignedMixinName(session, node);
@@ -150,7 +150,7 @@ public class NodeRemoveMixinTest extends AbstractJCRTest {
         Node node = testRootNode.addNode(nodeName1, testNodeType);
         // or try to make it lockable if it is not
         ensureMixinType(node, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         String mixinName = NodeMixinUtil.getAddableMixinName(session, node);
         if (mixinName == null) {
@@ -158,7 +158,7 @@ public class NodeRemoveMixinTest extends AbstractJCRTest {
         }
 
         node.addMixin(mixinName);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // remove first slash of path to get rel path to root
         String pathRelToRoot = node.getPath().substring(1);
@@ -208,7 +208,7 @@ public class NodeRemoveMixinTest extends AbstractJCRTest {
         Node node = testRootNode.addNode(nodeName1, testNodeType);
         // or try to make it versionable if it is not
         ensureMixinType(node, mixVersionable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         String mixinName = NodeMixinUtil.getAddableMixinName(session, node);
         if (mixinName == null || node.isNodeType(mixinName)) {
@@ -216,7 +216,7 @@ public class NodeRemoveMixinTest extends AbstractJCRTest {
         }
 
         node.addMixin(mixinName);
-        testRootNode.save();
+        testRootNode.getSession().save();
         node.checkin();
 
         try {
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeSetPrimaryTypeTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeSetPrimaryTypeTest.java
index 6322f4d..f4f7060 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeSetPrimaryTypeTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeSetPrimaryTypeTest.java
@@ -212,7 +212,7 @@ public class NodeSetPrimaryTypeTest extends AbstractJCRTest {
         Node node = testRootNode.addNode(nodeName1, testNodeType);
         // or try to make it lockable if it is not
         ensureMixinType(node, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         String primaryTypeName = getPrimaryTypeName(session, node);
         if (primaryTypeName == null) {
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeTest.java
index e5cec26..5ac2d7f 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/NodeTest.java
@@ -35,7 +35,7 @@ import javax.jcr.lock.LockException;
 
 /**
  * <code>NodeTest</code> contains all test cases for the
- * <code>javax.jcr.Node</code> that are related to writing, modifing or deleting
+ * <code>javax.jcr.Node</code> that are related to writing, modifying or deleting
  * nodes (level 2 of the specification).
  *
  * @test
@@ -49,7 +49,7 @@ public class NodeTest extends AbstractJCRTest {
 
     /**
      * to be able to test the update(String) and getCorrespondingNodePath(String)
-     * methods we need an addtional workspace
+     * methods we need an additional workspace
      */
     public void setUp() throws Exception {
         super.setUp();
@@ -202,7 +202,7 @@ public class NodeTest extends AbstractJCRTest {
             testNode.update(workspaceName);
             fail("Calling Node.update() on modified node should throw InvalidItemStateException");
         } catch (InvalidItemStateException e) {
-            // ok , works as expected
+            // ok, works as expected
         }
     }
 
@@ -255,7 +255,7 @@ public class NodeTest extends AbstractJCRTest {
 
         // check if property is still there
         assertTrue("Node got property removed after Node.update() eventhough node has no clone", testNode.hasProperty(propertyName1));
-        // check if node did not get childs suddenly
+        // check if node did not get children suddenly
         assertFalse("Node has children assigned after Node.update() eventhough node has no clone", testNode.hasNodes());
     }
 
@@ -304,7 +304,7 @@ public class NodeTest extends AbstractJCRTest {
         // call the update method on test node in default workspace
         defaultTestNode.update(workspaceName);
 
-        // ok first check if node has no longer propertis
+        // ok first check if node has no longer properties
         assertFalse("Node updated with Node.update() should have property removed", defaultTestNode.hasProperty(propertyName1));
         // ok check if the child has been added
         assertTrue("Node updated with Node.update() should have received childrens", defaultTestNode.hasNode(nodeName2));
@@ -457,7 +457,7 @@ public class NodeTest extends AbstractJCRTest {
     /**
      * Creates a new node using {@link Node#addNode(String)} , saves using
      * {@link javax.jcr.Node#save()} on parent node. Uses a second session to
-     * verify if the node has been safed.
+     * verify if the node has been saved.
      */
     public void testAddNodeParentSave() throws RepositoryException {
         // get default workspace test root node using superuser session
@@ -481,7 +481,7 @@ public class NodeTest extends AbstractJCRTest {
     /**
      * Creates a new node using {@link Node#addNode(String)} , saves using
      * {@link javax.jcr.Session#save()}. Uses a second session to verify if the
-     * node has been safed.
+     * node has been saved.
      */
     public void testAddNodeSessionSave() throws RepositoryException {
         // get default workspace test root node using superuser session
@@ -603,7 +603,7 @@ public class NodeTest extends AbstractJCRTest {
             defaultRootNode.getNode(nodeName1);
             fail("Permanently removed node should no longer be adressable using Parent Node's getNode() method");
         } catch (PathNotFoundException e) {
-            // ok , works as expected
+            // ok, works as expected
         }
     }
 
@@ -648,8 +648,6 @@ public class NodeTest extends AbstractJCRTest {
     public void testRemoveNodeLockedItself()
             throws LockException, NotExecutableException, RepositoryException {
 
-        Session session = testRootNode.getSession();
-
         if (!isSupported(Repository.OPTION_LOCKING_SUPPORTED)) {
             throw new NotExecutableException("Locking is not supported.");
         }
@@ -658,7 +656,7 @@ public class NodeTest extends AbstractJCRTest {
         Node node = testRootNode.addNode(nodeName1, testNodeType);
         // or try to make it lockable if it is not
         ensureMixinType(node, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // remove first slash of path to get rel path to root
         String pathRelToRoot = node.getPath().substring(1);
@@ -701,7 +699,7 @@ public class NodeTest extends AbstractJCRTest {
         ensureMixinType(node, mixLockable);
         // create a child node
         Node subNode = node.addNode(nodeName2, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // lock the node
         // remove first slash of path to get rel path to root
@@ -729,7 +727,7 @@ public class NodeTest extends AbstractJCRTest {
     }
 
     /**
-     * Tests object identity, meaning two nodes objects accuired through the
+     * Tests object identity, meaning two nodes objects acquired through the
      * same session must have the same properties and states.
      * <p>
      * Prerequisites: <ul> <li><code>javax.jcr.tck.nodetype</code> must allow
@@ -780,9 +778,9 @@ public class NodeTest extends AbstractJCRTest {
 
         // create a node and save it
         Node testNode1 = testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
-        // accuire the same node with a different session
+        // acquire the same node with a different session
         Session session = getHelper().getReadOnlySession();
         try {
             Node testNode2 = (Node) session.getItem(testNode1.getPath());
@@ -798,7 +796,7 @@ public class NodeTest extends AbstractJCRTest {
     }
 
     /**
-     * Checks if {@link Node#isModified()} works correcty for unmodified and
+     * Checks if {@link Node#isModified()} works correctly for unmodified and
      * modified nodes.
      */
     public void testIsModified() throws RepositoryException {
@@ -812,14 +810,14 @@ public class NodeTest extends AbstractJCRTest {
 
         assertFalse("Unmodified node should return false on Node.isModified()", testNode.isModified());
 
-        // check if modified properties are recognised
+        // check if modified properties are recognized
         testNode.setProperty(propertyName1, "test");
 
         assertTrue("Modified node should return true on Node.isModified()", testNode.isModified());
 
         defaultRootNode.save();
 
-        // check if modified child nodes are recognised
+        // check if modified child nodes are recognized
         testNode.addNode(nodeName2, testNodeType);
 
         assertTrue("Modified node should return true on Node.isModified()", testNode.isModified());
@@ -1042,11 +1040,11 @@ public class NodeTest extends AbstractJCRTest {
      * Tries to create and save a node using {@link javax.jcr.Node#save()} with
      * an mandatory property that is not set on saving time.
      * <p>
-     * Prerequisites: <ul> <li><code>javax.jcr.tck.Node.testSaveContstraintViolationException.nodetype2</code>
+     * Prerequisites: <ul> <li><code>javax.jcr.tck.Node.testSaveConstraintViolationException.nodetype2</code>
      * must reference a nodetype that has at least one property that is
      * mandatory but not autocreated</li> </ul>
      */
-    public void testSaveContstraintViolationException() throws RepositoryException {
+    public void testSaveConstraintViolationException() throws RepositoryException {
         // get default workspace test root node using superuser session
         Node defaultRootNode = (Node) superuser.getItem(testRootNode.getPath());
 
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/PropertyItemIsModifiedTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/PropertyItemIsModifiedTest.java
index 798e1dc..7cb0411 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/PropertyItemIsModifiedTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/PropertyItemIsModifiedTest.java
@@ -46,7 +46,7 @@ public class PropertyItemIsModifiedTest extends AbstractJCRTest {
         // build persistent node
         try {
             testNode = testRootNode.addNode(nodeName1, testNodeType);
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (RepositoryException e) {
             fail("Failed to create test node." + e.getMessage());
         }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/PropertyItemIsNewTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/PropertyItemIsNewTest.java
index 14cdf8c..3f414e4 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/PropertyItemIsNewTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/PropertyItemIsNewTest.java
@@ -46,7 +46,7 @@ public class PropertyItemIsNewTest extends AbstractJCRTest {
         // build persistent node
         try {
             testNode = testRootNode.addNode(nodeName1, testNodeType);
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (RepositoryException e) {
             fail("Failed to create test node." + e.getMessage());
         }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/PropertyTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/PropertyTest.java
index 74ee92b..18ee2bb 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/PropertyTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/PropertyTest.java
@@ -55,7 +55,7 @@ public class PropertyTest extends AbstractJCRTest {
         // create a node, add a property and save it
         Node testNode1 = testRootNode.addNode(nodeName1, testNodeType);
         Property prop1 = testNode1.setProperty(propertyName1, "value1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // accuire the same property through a different session
         Session session = getHelper().getSuperuserSession();
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ReferencesTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ReferencesTest.java
index 0e23e0c..a8bbd71 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ReferencesTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ReferencesTest.java
@@ -45,7 +45,7 @@ public class ReferencesTest extends AbstractJCRTest {
         ensureMixinType(n1, mixReferenceable);
 
         // with some impls. the mixin type has only affect upon save
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // make sure the node is now referenceable
         assertTrue("test node should be mix:referenceable", n1.isNodeType(mixReferenceable));
@@ -60,7 +60,7 @@ public class ReferencesTest extends AbstractJCRTest {
         ensureCanSetProperty(n2, propertyName1, values);
 
         Property p1 = n2.setProperty(propertyName1, values);
-        testRootNode.save();
+        testRootNode.getSession().save();
         PropertyIterator iter = n1.getReferences();
         if (iter.hasNext()) {
             assertEquals("Wrong referer", iter.nextProperty().getPath(), p1.getPath());
@@ -71,7 +71,7 @@ public class ReferencesTest extends AbstractJCRTest {
         // create references: n3.p1 -> n1
         Node n3 = testRootNode.addNode(nodeName3, testNodeType);
         n3.setProperty(propertyName1, n1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         iter = n1.getReferences();
         while (iter.hasNext()) {
             Property p = iter.nextProperty();
@@ -92,7 +92,7 @@ public class ReferencesTest extends AbstractJCRTest {
 
         // remove reference n3.p1 -> n1
         testRootNode.getNode(nodeName3).getProperty(propertyName1).remove();
-        testRootNode.save();
+        testRootNode.getSession().save();
         iter = n1.getReferences();
         if (iter.hasNext()) {
             assertEquals("Wrong referer", iter.nextProperty().getParent().getPath(), testRootNode.getNode(nodeName2).getPath());
@@ -105,7 +105,7 @@ public class ReferencesTest extends AbstractJCRTest {
 
         // remove reference n2.p1 -> n1
         testRootNode.getNode(nodeName2).getProperty(propertyName1).setValue(new Value[0]);
-        testRootNode.save();
+        testRootNode.getSession().save();
         iter = n1.getReferences();
         if (iter.hasNext()) {
             fail("too many referers: " + iter.nextProperty().getPath());
@@ -120,7 +120,7 @@ public class ReferencesTest extends AbstractJCRTest {
         ensureMixinType(n1, mixReferenceable);
 
         // with some impls. the mixin type has only affect upon save
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // make sure the node is now referenceable
         assertTrue("test node should be mix:referenceable", n1.isNodeType(mixReferenceable));
@@ -143,7 +143,7 @@ public class ReferencesTest extends AbstractJCRTest {
         Property p1 = n2.setProperty(propertyName1, values);
         Property p2 = n2.setProperty(propertyName2, values);
         Property p3 = n3.setProperty(propertyName1, n1);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // get references with name propertyName1
         // (should return p1 and p3))
@@ -168,7 +168,7 @@ public class ReferencesTest extends AbstractJCRTest {
 
         // remove reference n3.p1 -> n1
         testRootNode.getNode(nodeName3).getProperty(propertyName1).remove();
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // get references with name propertyName1
         // (should return p1))
@@ -182,7 +182,7 @@ public class ReferencesTest extends AbstractJCRTest {
 
         // remove reference n2.p1 -> n1
         p1.remove();
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // get references with name propertyName1
         // (should nothing))
@@ -202,7 +202,7 @@ public class ReferencesTest extends AbstractJCRTest {
         ensureMixinType(n1, mixReferenceable);
 
         // with some impls. the mixin type has only affect upon save
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // make sure the node is now referenceable
         assertTrue("test node should be mix:referenceable", n1.isNodeType(mixReferenceable));
@@ -215,10 +215,10 @@ public class ReferencesTest extends AbstractJCRTest {
         ensureCanSetProperty(n2, propertyName1, n2.getSession().getValueFactory().createValue(n1));
 
         n2.setProperty(propertyName1, n1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Wrong reference target.", n2.getProperty(propertyName1).getNode().getUUID(), n1.getUUID());
         n2.remove();
-        testRootNode.save();
+        testRootNode.getSession().save();
     }
 
     /**
@@ -231,7 +231,7 @@ public class ReferencesTest extends AbstractJCRTest {
         ensureMixinType(n2, mixReferenceable);
 
         // with some impls. the mixin type has only affect upon save
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // make sure the nodes are now referenceable
         assertTrue("test node should be mix:referenceable", n1.isNodeType(mixReferenceable));
@@ -245,7 +245,7 @@ public class ReferencesTest extends AbstractJCRTest {
         ensureCanSetProperty(n3, propertyName1, n3.getSession().getValueFactory().createValue(n1));
 
         n3.setProperty(propertyName1, n1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Wrong reference target.", n3.getProperty(propertyName1).getNode().getUUID(), n1.getUUID());
         PropertyIterator iter = n1.getReferences();
         if (iter.hasNext()) {
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SerializationTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SerializationTest.java
index 15444e1..c74f538 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SerializationTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SerializationTest.java
@@ -79,12 +79,13 @@ public class SerializationTest extends AbstractJCRTest {
         try {
             session = superuser;
             workspace = session.getWorkspace();
-            file = File.createTempFile("serializationTest", ".xml");
-            log.print("Tempfile: " + file.getAbsolutePath());
 
             SerializationContext sc = new SerializationContext(this, session);
             treeComparator = new TreeComparator(sc, session);
             treeComparator.createComplexTree(treeComparator.WORKSPACE);
+
+            file = File.createTempFile("serializationTest", ".xml");
+            log.print("Tempfile: " + file.getAbsolutePath());
         }
         catch (Exception ex) {
             if (file != null) {
@@ -208,7 +209,7 @@ public class SerializationTest extends AbstractJCRTest {
             //A LockException is thrown if a lock prevents the addition of the subtree.
             Node lNode = testRootNode.addNode(nodeName1);
             ensureMixinType(lNode, mixLockable);
-            testRootNode.save();
+            testRootNode.getSession().save();
             Lock lock = lNode.lock(true, true);
             session.removeLockToken(lock.getLockToken());   //remove the token, so the lock is for me, too
             FileInputStream in = new FileInputStream(file);
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SessionRemoveItemTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SessionRemoveItemTest.java
index c01eaf2..a770897 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SessionRemoveItemTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SessionRemoveItemTest.java
@@ -47,7 +47,7 @@ public class SessionRemoveItemTest extends AbstractJCRTest {
         readOnlySession = getHelper().getReadOnlySession();
 
         removeNode = testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         nPath = removeNode.getPath();
     }
 
@@ -75,6 +75,7 @@ public class SessionRemoveItemTest extends AbstractJCRTest {
 
     public void testRemoveItem3() throws RepositoryException {
         adminSession.removeItem(nPath);
+        readOnlySession.refresh(false); // see JCR-3302
 
         // node must still exist for another session.
         assertTrue(readOnlySession.nodeExists(nPath));
@@ -82,6 +83,8 @@ public class SessionRemoveItemTest extends AbstractJCRTest {
 
     public void testRemoveItem4() throws RepositoryException {
         try {
+            readOnlySession.refresh(false); // see JCR-3302
+
             readOnlySession.removeItem(nPath);
             readOnlySession.save();
             fail("A read-only session must not be allowed to remove an item");
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SessionTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SessionTest.java
index 57f0f85..d16f9f9 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SessionTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SessionTest.java
@@ -16,19 +16,19 @@
  */
 package org.apache.jackrabbit.test.api;
 
-import org.apache.jackrabbit.test.AbstractJCRTest;
-import org.apache.jackrabbit.test.NotExecutableException;
-
-import javax.jcr.nodetype.ConstraintViolationException;
-import javax.jcr.RepositoryException;
-import javax.jcr.Node;
+import javax.jcr.InvalidItemStateException;
 import javax.jcr.ItemExistsException;
+import javax.jcr.Node;
 import javax.jcr.PathNotFoundException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
 import javax.jcr.Session;
-import javax.jcr.InvalidItemStateException;
 import javax.jcr.Value;
-import javax.jcr.Repository;
 import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.jackrabbit.test.NotExecutableException;
 
 /**
  * <code>SessionTest</code> contains all test cases for the
@@ -235,7 +235,7 @@ public class SessionTest extends AbstractJCRTest {
         // save only moved node
         try {
             destParentNode.save();
-            fail("Saving only moved node after a Session.move() operation should throw ContstraintViolationException");
+            fail("Saving only moved node after a Session.move() operation should throw ConstraintViolationException");
         } catch (ConstraintViolationException e) {
             // ok try to save the source
         }
@@ -264,7 +264,7 @@ public class SessionTest extends AbstractJCRTest {
         // add a sub node (the one that is tried to move later on)
         Node srcNode = lockableNode.addNode(nodeName1, testNodeType);
 
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // remove first slash of path to get rel path to root
         String pathRelToRoot = lockableNode.getPath().substring(1);
@@ -278,7 +278,7 @@ public class SessionTest extends AbstractJCRTest {
             try {
                 String destPath = testRoot + "/" + nodeName2;
                 session.move(srcNode.getPath(), destPath);
-                testRootNode.save();
+                testRootNode.getSession().save();
                 fail("A LockException is thrown either immediately or on save  if a lock prevents the move.");
             } catch (LockException e){
                 // success
@@ -401,11 +401,11 @@ public class SessionTest extends AbstractJCRTest {
      * Tries to create and save a node using {@link javax.jcr.Session#save()}
      * with an mandatory property that is not set on saving time.
      * <p>
-     * Prerequisites: <ul> <li><code>javax.jcr.tck.SessionTest.testSaveContstraintViolationException.nodetype2</code>
+     * Prerequisites: <ul> <li><code>javax.jcr.tck.SessionTest.testSaveConstraintViolationException.nodetype2</code>
      * must reference a nodetype that has one at least one property that is
      * mandatory but not autocreated</li> </ul>
      */
-    public void testSaveContstraintViolationException() throws RepositoryException {
+    public void testSaveConstraintViolationException() throws RepositoryException {
         // get default workspace test root node using superuser session
         Node defaultRootNode = (Node) superuser.getItem(testRootNode.getPath());
 
@@ -671,20 +671,21 @@ public class SessionTest extends AbstractJCRTest {
     /**
      * Checks if {@link javax.jcr.Session#hasCapability(String, Object, Object[])}
      * works as specified.
-     * <p/>
+     * <p>
      *
      * @throws RepositoryException
      */
     public void testHasCapability() throws RepositoryException {
         Session roSession = getHelper().getReadOnlySession();
         try {
-            Node root = roSession.getRootNode();
+            Node testRoot = roSession.getNode(testRootNode.getPath());
             Object[] args = new Object[] { "foo" };
-            if (!roSession.hasCapability("addNode",  root, args)) {
+            if (!roSession.hasCapability("addNode",  testRoot, args)) {
                 // if hasCapability() returns false, the actual method call
                 // is expected to fail
                 try {
-                    root.addNode("foo");
+                    testRoot.addNode("foo");
+                    roSession.save();
                     fail("Node.addNode() should fail according to Session.hasCapability()");
                 } catch (RepositoryException e) {
                     // expected 
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyAssumeTypeTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyAssumeTypeTest.java
index 672d76a..80d5f22 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyAssumeTypeTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyAssumeTypeTest.java
@@ -372,7 +372,7 @@ public class SetPropertyAssumeTypeTest extends AbstractJCRTest {
         ensureMixinType(referenceableNode, mixReferenceable);
 
         // some implementations may require a save after addMixin()
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         Property prop = testNode.setProperty(testPropName, referenceableNode);
         assertEquals("setProperty(String, Value) of a property of type undefined " +
@@ -456,7 +456,7 @@ public class SetPropertyAssumeTypeTest extends AbstractJCRTest {
             cal.setTime(new Date(0));
             Value v = superuser.getValueFactory().createValue(ISO8601.format(cal));
             testNode.setProperty(propertyName1, v, PropertyType.DATE);
-            testRootNode.save();
+            testRootNode.getSession().save();
             fail("Node.setProperty(String, Value, int) must throw a " +
                  "ConstraintViolationExcpetion if the type parameter and the " +
                  "type of the property do not match." );
@@ -479,7 +479,7 @@ public class SetPropertyAssumeTypeTest extends AbstractJCRTest {
             Calendar cal = Calendar.getInstance();
             cal.setTime(new Date(0));
             testNode.setProperty(propertyName1, ISO8601.format(cal), PropertyType.DATE);
-            testRootNode.save();
+            testRootNode.getSession().save();
             fail("Node.setProperty(String, Value, int) must throw a " +
                  "ConstraintViolationExcpetion if the type parameter and the " +
                  "type of the property do not match." );
@@ -500,7 +500,7 @@ public class SetPropertyAssumeTypeTest extends AbstractJCRTest {
 
         try {
             testNode.setProperty(propertyName1, stringValues, PropertyType.DATE);
-            testRootNode.save();
+            testRootNode.getSession().save();
             fail("Node.setProperty(String, Value, int) must throw a " +
                     "ConstraintViolationExcpetion or a ValueFormatException if " +
                     "the type parameter and the type of the property do not match.");
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyBooleanTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyBooleanTest.java
index 856102c..deefbce 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyBooleanTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyBooleanTest.java
@@ -76,7 +76,7 @@ public class SetPropertyBooleanTest extends AbstractJCRTest {
      */
     public void testNewBooleanPropertyParent() throws Exception {
         testNode.setProperty(propertyName1, true);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Setting property with Node.setProperty(String, boolean) and parentNode.save() not working",
                 true,
                 testNode.getProperty(propertyName1).getBoolean());
@@ -88,9 +88,9 @@ public class SetPropertyBooleanTest extends AbstractJCRTest {
      */
     public void testModifyBooleanPropertyParent() throws Exception {
         testNode.setProperty(propertyName1, true);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName1, false);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Modifying property with Node.setProperty(String, boolean) and parentNode.save() not working",
                 false,
                 testNode.getProperty(propertyName1).getBoolean());
@@ -117,9 +117,9 @@ public class SetPropertyBooleanTest extends AbstractJCRTest {
      */
     public void testRemoveBooleanPropertyParent() throws Exception {
         testNode.setProperty(propertyName1, true);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName1, (Value) null);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertFalse("Removing boolean property with Node.setProperty(String, null) and parentNode.save() not working",
                 testNode.hasProperty(propertyName1));
     }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyCalendarTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyCalendarTest.java
index db0e442..6599c82 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyCalendarTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyCalendarTest.java
@@ -81,7 +81,7 @@ public class SetPropertyCalendarTest extends AbstractJCRTest {
      */
     public void testNewCalendarPropertyParent() throws Exception {
         testNode.setProperty(propertyName1, c1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Setting property with Node.setProperty(String, Calendar) and parentNode.save() not working",
                 vf.createValue(c1),
                 testNode.getProperty(propertyName1).getValue());
@@ -93,9 +93,9 @@ public class SetPropertyCalendarTest extends AbstractJCRTest {
      */
     public void testModifyCalendarPropertyParent() throws Exception {
         testNode.setProperty(propertyName1, c1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName1, c2);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Modifying property with Node.setProperty(String, Calendar) and parentNode.save() not working",
                 vf.createValue(c2),
                 testNode.getProperty(propertyName1).getValue());
@@ -122,9 +122,9 @@ public class SetPropertyCalendarTest extends AbstractJCRTest {
      */
     public void testRemoveCalendarPropertyParent() throws Exception {
         testNode.setProperty(propertyName1, c1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName1, (Calendar) null);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertFalse("Removing property with Node.setProperty(String, (Calendar)null) and parentNode.save() not working",
                 testNode.hasProperty(propertyName1));
     }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyConstraintViolationExceptionTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyConstraintViolationExceptionTest.java
index a32b911..dc47274 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyConstraintViolationExceptionTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyConstraintViolationExceptionTest.java
@@ -18,6 +18,8 @@ package org.apache.jackrabbit.test.api;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
 
 import javax.jcr.Node;
 import javax.jcr.PropertyType;
@@ -75,7 +77,7 @@ public class SetPropertyConstraintViolationExceptionTest extends AbstractJCRTest
         try {
             String nodeType = propDef.getDeclaringNodeType().getName();
             node = testRootNode.addNode(nodeName2, nodeType);
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (ConstraintViolationException e) {
             // implementation specific constraints do not allow to set up test environment
             throw new NotExecutableException("Not able to create required test items.");
@@ -134,7 +136,7 @@ public class SetPropertyConstraintViolationExceptionTest extends AbstractJCRTest
         try {
             String nodeType = propDef.getDeclaringNodeType().getName();
             node = testRootNode.addNode(nodeName2, nodeType);
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (ConstraintViolationException e) {
             // implementation specific constraints do not allow to set up test environment
             throw new NotExecutableException("Not able to create required test items.");
@@ -193,7 +195,7 @@ public class SetPropertyConstraintViolationExceptionTest extends AbstractJCRTest
         try {
             String nodeType = propDef.getDeclaringNodeType().getName();
             node = testRootNode.addNode(nodeName2, nodeType);
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (ConstraintViolationException e) {
             // implementation specific constraints do not allow to set up test environment
             throw new NotExecutableException("Not able to create required test items.");
@@ -253,7 +255,7 @@ public class SetPropertyConstraintViolationExceptionTest extends AbstractJCRTest
         try {
             String nodeType = propDef.getDeclaringNodeType().getName();
             node = testRootNode.addNode(nodeName2, nodeType);
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (ConstraintViolationException e) {
             // implementation specific constraints do not allow to set up test environment
             throw new NotExecutableException("Not able to create required test items.");
@@ -315,7 +317,7 @@ public class SetPropertyConstraintViolationExceptionTest extends AbstractJCRTest
         try {
             String nodeType = propDef.getDeclaringNodeType().getName();
             node = testRootNode.addNode(nodeName2, nodeType);
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (ConstraintViolationException e) {
             // implementation specific constraints do not allow to set up test environment
             throw new NotExecutableException("Not able to create required test items.");
@@ -362,25 +364,31 @@ public class SetPropertyConstraintViolationExceptionTest extends AbstractJCRTest
                     "testable value constraints has been found");
         }
 
-        String constraints[] = propDef.getValueConstraints();
+        String valueConstraints[] = propDef.getValueConstraints();
+        if (valueConstraints == null || valueConstraints.length == 0) {
+            throw new NotExecutableException("No reference property def with "
+                    + "testable value constraints has been found");
+        }
+        List<String> constraints = Arrays.asList(valueConstraints);
         String nodeTypeNotSatisfied = null;
 
         NodeTypeManager manager = superuser.getWorkspace().getNodeTypeManager();
         NodeTypeIterator types = manager.getAllNodeTypes();
 
         // find a NodeType which is not satisfying the constraints
-        findNodeTypeNotSatisfied:
-            while (types.hasNext()) {
-                NodeType type = types.nextNodeType();
-                String name = type.getName();
-                for (int i = 0; i < constraints.length; i++) {
-                    if (name.equals(constraints[i]) || ntFrozenNode.equals(name)) {
-                        continue findNodeTypeNotSatisfied;
-                    }
-                    nodeTypeNotSatisfied = name;
-                    break findNodeTypeNotSatisfied;
-                }
+        while (types.hasNext()) {
+            NodeType type = types.nextNodeType();
+            String name = type.getName();
+            if (constraints.contains(name) || ntFrozenNode.equals(name)) {
+                continue;
+            }
+            if (type.getChildNodeDefinitions() != null
+                    && type.getChildNodeDefinitions().length > 0) {
+                continue;
             }
+            nodeTypeNotSatisfied = name;
+            break;
+        }
 
         if (nodeTypeNotSatisfied == null) {
             throw new NotExecutableException("No reference property def with " +
@@ -398,7 +406,7 @@ public class SetPropertyConstraintViolationExceptionTest extends AbstractJCRTest
             nodeNotSatisfied = testRootNode.addNode(nodeName4, nodeTypeNotSatisfied);
             ensureMixinType(nodeNotSatisfied, mixReferenceable);
 
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (ConstraintViolationException e) {
             // implementation specific constraints do not allow to set up test environment
             throw new NotExecutableException("Not able to create required test items.");
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyDoubleTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyDoubleTest.java
index 6ac3728..5cb9ad2 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyDoubleTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyDoubleTest.java
@@ -83,7 +83,7 @@ public class SetPropertyDoubleTest extends AbstractJCRTest {
      */
     public void testNewDoublePropertyParent() throws Exception {
         testNode.setProperty(propertyName1, d1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Setting property with Node.setProperty(String, double) and parentNode.save() not working",
                 new Double(d1),
                 new Double(testNode.getProperty(propertyName1).getDouble()));
@@ -95,9 +95,9 @@ public class SetPropertyDoubleTest extends AbstractJCRTest {
      */
     public void testModifyDoublePropertyParent() throws Exception {
         testNode.setProperty(propertyName1, d1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName1, d2);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Modifying property with Node.setProperty(String, double) and parentNode.save() not working",
                 new Double(d2),
                 new Double(testNode.getProperty(propertyName1).getDouble()));
@@ -124,11 +124,24 @@ public class SetPropertyDoubleTest extends AbstractJCRTest {
      */
     public void testRemoveDoublePropertyParent() throws Exception {
         testNode.setProperty(propertyName1, d1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName1, (Value) null);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertFalse("Removing double property with Node.setProperty(String, null) and parentNode.save() not working",
                 testNode.hasProperty(propertyName1));
     }
 
+    /**
+     * Tests that in infinity and NaN values can be persisted and round-tripped.
+     */
+    public void testEdgeCases() throws Exception {
+        double tests[] = { Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY };
+        String path = testNode.getPath();
+
+        for (double v : tests) {
+            testNode.setProperty(propertyName1, v);
+            testRootNode.getSession().save();
+            assertEquals("Round-trip of " + v, v, superuser.getNode(path).getProperty(propertyName1).getDouble());
+        }
+    }
 }
\ No newline at end of file
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyInputStreamTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyInputStreamTest.java
index 89f2dba..130cdfb 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyInputStreamTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyInputStreamTest.java
@@ -48,7 +48,7 @@ public class SetPropertyInputStreamTest extends AbstractJCRTest {
     protected void setUp() throws Exception {
         super.setUp();
         testNode = testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // special case for repositories that do allow binary property
         // values, but only on jcr:content/jcr:data
@@ -105,7 +105,7 @@ public class SetPropertyInputStreamTest extends AbstractJCRTest {
      */
     public void testNewInputStreamPropertyParent() throws Exception {
         testNode.setProperty(propertyName1, is1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         is1 = new ByteArrayInputStream(bytes1);
         InputStream in = testNode.getProperty(propertyName1).getStream();
         try {
@@ -122,9 +122,9 @@ public class SetPropertyInputStreamTest extends AbstractJCRTest {
      */
     public void testModifyInputStreamPropertyParent() throws Exception {
         testNode.setProperty(propertyName1, is1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName1, is2);
-        testRootNode.save();
+        testRootNode.getSession().save();
         is2 = new ByteArrayInputStream(bytes2);
         InputStream in = testNode.getProperty(propertyName1).getStream();
         try {
@@ -162,7 +162,7 @@ public class SetPropertyInputStreamTest extends AbstractJCRTest {
      */
     public void testRemoveInputStreamPropertyParent() throws Exception {
         testNode.setProperty(propertyName1, is1);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         Property property = testNode.getProperty(propertyName1);
         if (property.getDefinition().isMandatory() || property.getDefinition().isProtected()) {
@@ -170,7 +170,7 @@ public class SetPropertyInputStreamTest extends AbstractJCRTest {
         }
 
         testNode.setProperty(propertyName1, (InputStream) null);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertFalse("Removing property with Node.setProperty(String, (InputStream)null) and parentNode.save() not working",
                 testNode.hasProperty(propertyName1));
     }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyLongTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyLongTest.java
index 2981d97..bafd4ba 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyLongTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyLongTest.java
@@ -79,7 +79,7 @@ public class SetPropertyLongTest extends AbstractJCRTest {
      */
     public void testNewLongPropertyParent() throws Exception {
         testNode.setProperty(propertyName1, l1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Setting property with Node.setProperty(String, long) and parentNode.save() not working",
                 new Long(l1),
                 new Long(testNode.getProperty(propertyName1).getLong()));
@@ -91,9 +91,9 @@ public class SetPropertyLongTest extends AbstractJCRTest {
      */
     public void testModifyLongPropertyParent() throws Exception {
         testNode.setProperty(propertyName1, l1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName1, l2);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Modifying property with Node.setProperty(String, long) and parentNode.save() not working",
                 new Long(l2),
                 new Long(testNode.getProperty(propertyName1).getLong()));
@@ -120,9 +120,9 @@ public class SetPropertyLongTest extends AbstractJCRTest {
      */
     public void testRemoveLongPropertyParent() throws Exception {
         testNode.setProperty(propertyName1, l1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName1, (Value) null);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertFalse("Removing long property with Node.setProperty(String, null) and parentNode.save() not working",
                 testNode.hasProperty(propertyName1));
     }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyNodeTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyNodeTest.java
index f83f179..7709242 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyNodeTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyNodeTest.java
@@ -50,7 +50,7 @@ public class SetPropertyNodeTest extends AbstractJCRTest {
         => call save in order to make sure, that the uuid is created and contains
            a value generated by the system.
         */
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // abort test if the repository does not allow setting
         // reference properties on this node
@@ -103,7 +103,7 @@ public class SetPropertyNodeTest extends AbstractJCRTest {
      */
     public void testNewNodePropertyParent() throws Exception {
         testNode.setProperty(propertyName1, n1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Setting property with Node.setProperty(String, Node) and parentNode.save() not working",
                 n1.getUUID(),
                 testNode.getProperty(propertyName1).getString());
@@ -115,9 +115,9 @@ public class SetPropertyNodeTest extends AbstractJCRTest {
      */
     public void testModifyNodePropertyParent() throws Exception {
         testNode.setProperty(propertyName1, n1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName1, n2);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Modifying property with Node.setProperty(String, Node) and parentNode.save() not working",
                 n2.getUUID(),
                 testNode.getProperty(propertyName1).getString());
@@ -144,9 +144,9 @@ public class SetPropertyNodeTest extends AbstractJCRTest {
      */
     public void testRemoveNodePropertyParent() throws Exception {
         testNode.setProperty(propertyName1, n1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName1, (Node) null);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertFalse("Removing property with Node.setProperty(String, (Node)null) and parentNode.save() not working",
                 testNode.hasProperty(propertyName1));
     }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyStringTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyStringTest.java
index f29af67..76d360c 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyStringTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyStringTest.java
@@ -120,7 +120,7 @@ public class SetPropertyStringTest extends AbstractJCRTest {
      */
     public void testNewStringPropertyParent() throws Exception {
         testNode.setProperty(propertyName1, s1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Setting property with Node.setProperty(String, String) and parentNode.save() not working",
                 s1,
                 testNode.getProperty(propertyName1).getString());
@@ -132,9 +132,9 @@ public class SetPropertyStringTest extends AbstractJCRTest {
      */
     public void testModifyStringPropertyParent() throws Exception {
         testNode.setProperty(propertyName1, s1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName1, s2);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Modifying property with Node.setProperty(String, String) and parentNode.save() not working",
                 s2,
                 testNode.getProperty(propertyName1).getString());
@@ -161,9 +161,9 @@ public class SetPropertyStringTest extends AbstractJCRTest {
      */
     public void testRemoveStringPropertyParent() throws Exception {
         testNode.setProperty(propertyName1, s1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName1, (String) null);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertFalse("Removing property with Node.setProperty(String, (String)null) and parentNode.save() not working",
                 testNode.hasProperty(propertyName1));
     }
@@ -203,7 +203,7 @@ public class SetPropertyStringTest extends AbstractJCRTest {
      */
     public void testNewStringPropertyParentWithPropertyType() throws Exception {
         testNode.setProperty(propertyName1, s1, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Setting property with Node.setProperty(String, String, int) and parentNode.save() not working",
                 s1,
                 testNode.getProperty(propertyName1).getString());
@@ -215,9 +215,9 @@ public class SetPropertyStringTest extends AbstractJCRTest {
      */
     public void testModifyStringPropertyParentWithPropertyType() throws Exception {
         testNode.setProperty(propertyName1, s1, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName1, s2, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Modifying property with Node.setProperty(String, String, int) and parentNode.save() not working",
                 s2,
                 testNode.getProperty(propertyName1).getString());
@@ -244,9 +244,9 @@ public class SetPropertyStringTest extends AbstractJCRTest {
      */
     public void testRemoveStringPropertyParentWithPropertyType() throws Exception {
         testNode.setProperty(propertyName1, s1, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName1, (String) null, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertFalse("Removing property with Node.setProperty(String, (String)null, int) and parentNode.save() not working",
                 testNode.hasProperty(propertyName1));
     }
@@ -286,7 +286,7 @@ public class SetPropertyStringTest extends AbstractJCRTest {
      */
     public void testNewStringArrayPropertyParent() throws Exception {
         testNode.setProperty(propertyName2, sArray1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Setting properties with Node.setProperty(String, String[]) and parentNode.save() not working",
                 Arrays.asList(vArray1),
                 Arrays.asList(testNode.getProperty(propertyName2).getValues()));
@@ -298,34 +298,15 @@ public class SetPropertyStringTest extends AbstractJCRTest {
      */
     public void testModifyStringArrayPropertyParent() throws Exception {
         testNode.setProperty(propertyName2, sArray1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName2, sArray2);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Modifying properties with Node.setProperty(String, String[]) and parentNode.save() not working",
                 Arrays.asList(vArray2),
                 Arrays.asList(testNode.getProperty(propertyName2).getValues()));
     }
 
     /**
-     * Tests if <code>Node.setProperty(String, String[])</code> throws a {@link
-     * javax.jcr.ValueFormatException} when trying to set an existing
-     * single-valued property to a multi-value
-     */
-    public void testSetSingleStringArrayValueFormatException() throws Exception {
-        // prerequisite: existing single-valued STRING property
-        if (!testNode.hasProperty(propertyName1)) {
-            testNode.setProperty(propertyName1, s1);
-            testNode.getParent().save();
-        }
-
-        try {
-            testNode.setProperty(propertyName1, sArray1);
-            fail("setProperty(singleValueProperty, String[]) not throwing a ValueFormatException");
-        } catch (ValueFormatException success) {
-        }
-    }
-
-    /**
      * Tests if removing a <code>String[]</code> property with
      * <code>Node.setProperty(String, null)</code> works with
      * <code>Session.save()</code>
@@ -346,9 +327,9 @@ public class SetPropertyStringTest extends AbstractJCRTest {
      */
     public void testRemoveStringArrayPropertyParent() throws Exception {
         testNode.setProperty(propertyName2, sArray1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName2, (String[]) null);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertFalse("Removing property with Node.setProperty(String, (String[])null) and parentNode.save() not working",
                 testNode.hasProperty(propertyName2));
     }
@@ -400,7 +381,7 @@ public class SetPropertyStringTest extends AbstractJCRTest {
      */
     public void testNewStringArrayPropertyParentWithPropertyType() throws Exception {
         testNode.setProperty(propertyName2, sArray1, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Setting properties with Node.setProperty(String, String[], int) and parentNode.save() not working",
                 Arrays.asList(vArray1),
                 Arrays.asList(testNode.getProperty(propertyName2).getValues()));
@@ -412,34 +393,15 @@ public class SetPropertyStringTest extends AbstractJCRTest {
      */
     public void testModifyStringArrayPropertyParentWithPropertyType() throws Exception {
         testNode.setProperty(propertyName2, sArray1, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName2, sArray2, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Modifying properties with Node.setProperty(String, String[], int) and parentNode.save() not working",
                 Arrays.asList(vArray2),
                 Arrays.asList(testNode.getProperty(propertyName2).getValues()));
     }
 
     /**
-     * Tests if <code>Node.setProperty(String, String[], int)</code> throws a
-     * {@link javax.jcr.ValueFormatException} when trying to set an existing
-     * single-value property to a multi-value
-     */
-    public void testSetSingleStringArrayValueFormatExceptionWithPropertyType() throws Exception {
-        // prerequisite: existing single-valued STRING property
-        if (!testNode.hasProperty(propertyName1)) {
-            testNode.setProperty(propertyName1, s1);
-            testNode.getParent().save();
-        }
-
-        try {
-            testNode.setProperty(propertyName1, sArray1, PropertyType.STRING);
-            fail("setProperty(singleValueProperty, String[], int) not throwing a ValueFormatException");
-        } catch (ValueFormatException success) {
-        }
-    }
-
-    /**
      * Tests if removing a <code>String[]</code> property with
      * <code>Node.setProperty(String, null, int)</code> works with
      * <code>Session.save()</code>
@@ -460,9 +422,9 @@ public class SetPropertyStringTest extends AbstractJCRTest {
      */
     public void testRemoveStringArrayPropertyParentWithPropertyType() throws Exception {
         testNode.setProperty(propertyName2, sArray1, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName2, (String[]) null, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertFalse("Removing property with Node.setProperty(String, (String[])null, int) and parentNode.save() not working",
                 testNode.hasProperty(propertyName2));
     }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyValueTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyValueTest.java
index a1232fb..5b45ade 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyValueTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetPropertyValueTest.java
@@ -130,7 +130,7 @@ public class SetPropertyValueTest extends AbstractJCRTest {
      */
     public void testNewValuePropertyParent() throws Exception {
         testNode.setProperty(propertyName1, v1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Setting property with Node.setProperty(String, Value) and parentNode.save() not working",
                 v1,
                 testNode.getProperty(propertyName1).getValue());
@@ -142,9 +142,9 @@ public class SetPropertyValueTest extends AbstractJCRTest {
      */
     public void testModifyValuePropertyParent() throws Exception {
         testNode.setProperty(propertyName1, v1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName1, v2);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Modifying property with Node.setProperty(String, Value) and parentNode.save() not working",
                 v2,
                 testNode.getProperty(propertyName1).getValue());
@@ -171,9 +171,9 @@ public class SetPropertyValueTest extends AbstractJCRTest {
      */
     public void testRemoveValuePropertyParent() throws Exception {
         testNode.setProperty(propertyName1, v1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName1, (Value) null);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertFalse("Removing property with Node.setProperty(String, (Value)null) and parentNode.save() not working",
                 testNode.hasProperty(propertyName1));
     }
@@ -214,7 +214,7 @@ public class SetPropertyValueTest extends AbstractJCRTest {
      */
     public void testNewValuePropertyParentWithPropertyType() throws Exception {
         testNode.setProperty(propertyName1, v1, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Setting property with Node.setProperty(String, Value, int) and parentNode.save() not working",
                 v1,
                 testNode.getProperty(propertyName1).getValue());
@@ -226,9 +226,9 @@ public class SetPropertyValueTest extends AbstractJCRTest {
      */
     public void testModifyValuePropertyParentWithPropertyType() throws Exception {
         testNode.setProperty(propertyName1, v1, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName1, v2, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Modifying property with Node.setProperty(String, Value, int) and parentNode.save() not working",
                 v2,
                 testNode.getProperty(propertyName1).getValue());
@@ -255,9 +255,9 @@ public class SetPropertyValueTest extends AbstractJCRTest {
      */
     public void testRemoveValuePropertyParentWithPropertyType() throws Exception {
         testNode.setProperty(propertyName1, v1, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName1, (Value) null, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertFalse("Removing property with Node.setProperty(String, (Value)null, int) and parentNode.save() not working",
                 testNode.hasProperty(propertyName1));
     }
@@ -298,7 +298,7 @@ public class SetPropertyValueTest extends AbstractJCRTest {
      */
     public void testNewValueArrayPropertyParent() throws Exception {
         testNode.setProperty(propertyName2, vArray1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Setting properties with Node.setProperty(String, Value[]) and parentNode.save() not working",
                 Arrays.asList(vArray1),
                 Arrays.asList(testNode.getProperty(propertyName2).getValues()));
@@ -310,9 +310,9 @@ public class SetPropertyValueTest extends AbstractJCRTest {
      */
     public void testModifyValueArrayPropertyParent() throws Exception {
         testNode.setProperty(propertyName2, vArray1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName2, vArray2);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Modifying properties with Node.setProperty(String, Value[]) and parentNode.save() not working",
                 Arrays.asList(vArray2),
                 Arrays.asList(testNode.getProperty(propertyName2).getValues()));
@@ -332,25 +332,6 @@ public class SetPropertyValueTest extends AbstractJCRTest {
     }
 
     /**
-     * Tests if <code>Node.setProperty(String, Value[])</code> throws a {@link
-     * javax.jcr.ValueFormatException} when trying to set an existing
-     * single-valued property to a multi-value
-     */
-    public void testSetSingleValueArrayValueFormatException() throws Exception {
-        // prerequisite: existing single-valued property
-        if (!testNode.hasProperty(propertyName1)) {
-            testNode.setProperty(propertyName1, v1);
-            testNode.getParent().save();
-        }
-
-        try {
-            testNode.setProperty(propertyName1, vArray1);
-            fail("setProperty(singleValueProperty, Value[]) not throwing a ValueFormatException");
-        } catch (ValueFormatException success) {
-        }
-    }
-
-    /**
      * Tests if removing a <code>Value[]</code> property with
      * <code>Node.setProperty(String, null)</code> works with
      * <code>Session.save()</code>
@@ -371,9 +352,9 @@ public class SetPropertyValueTest extends AbstractJCRTest {
      */
     public void testRemoveValueArrayPropertyParent() throws Exception {
         testNode.setProperty(propertyName2, vArray1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName2, (Value[]) null);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertFalse("Removing property with Node.setProperty(String, (Value[])null) and parentNode.save() not working",
                 testNode.hasProperty(propertyName2));
     }
@@ -438,7 +419,7 @@ public class SetPropertyValueTest extends AbstractJCRTest {
      */
     public void testNewValueArrayPropertyParentWithPropertyType() throws Exception {
         testNode.setProperty(propertyName2, vArray1, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Setting properties with Node.setProperty(String, Value[], int) and parentNode.save() not working",
                 Arrays.asList(vArray1),
                 Arrays.asList(testNode.getProperty(propertyName2).getValues()));
@@ -450,9 +431,9 @@ public class SetPropertyValueTest extends AbstractJCRTest {
      */
     public void testModifyValueArrayPropertyParentWithPropertyType() throws Exception {
         testNode.setProperty(propertyName2, vArray1, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName2, vArray2, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Modifying properties with Node.setProperty(String, Value[], int) and parentNode.save() not working",
                 Arrays.asList(vArray2),
                 Arrays.asList(testNode.getProperty(propertyName2).getValues()));
@@ -472,25 +453,6 @@ public class SetPropertyValueTest extends AbstractJCRTest {
     }
 
     /**
-     * Tests if <code>Node.setProperty(String, Value[], int)</code> throws a
-     * {@link javax.jcr.ValueFormatException} when trying to set an existing
-     * single-valued property to a multi-value
-     */
-    public void testSetSingleValueArrayValueFormatExceptionWithPropertyType() throws Exception {
-        // prerequisite: existing single-valued property
-        if (!testNode.hasProperty(propertyName1)) {
-            testNode.setProperty(propertyName1, v1);
-            testNode.getParent().save();
-        }
-
-        try {
-            testNode.setProperty(propertyName1, vArray1, PropertyType.STRING);
-            fail("setProperty(singleValueProperty, Value[], int) not throwing a ValueFormatException");
-        } catch (ValueFormatException success) {
-        }
-    }
-
-    /**
      * Tests if removing a <code>Value[]</code> property with
      * <code>Node.setProperty(String, null, int)</code> works with
      * <code>Session.save()</code>
@@ -511,9 +473,9 @@ public class SetPropertyValueTest extends AbstractJCRTest {
      */
     public void testRemoveValueArrayPropertyParentWithPropertyType() throws Exception {
         testNode.setProperty(propertyName2, vArray1, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.setProperty(propertyName2, (Value[]) null, PropertyType.STRING);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertFalse("Removing property with Node.setProperty(String, (Value[])null, int) and parentNode.save() not working",
                 testNode.hasProperty(propertyName2));
     }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueBinaryTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueBinaryTest.java
index 7a957ee..3c6620a 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueBinaryTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueBinaryTest.java
@@ -78,7 +78,7 @@ public class SetValueBinaryTest extends AbstractJCRTest {
 
         // create a new node under the testRootNode
         node = testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // special case for repositories that do allow binary property
         // values, but only on jcr:content/jcr:data
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueConstraintViolationExceptionTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueConstraintViolationExceptionTest.java
index 95b2f09..6e37e42 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueConstraintViolationExceptionTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueConstraintViolationExceptionTest.java
@@ -18,6 +18,8 @@ package org.apache.jackrabbit.test.api;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
 
 import javax.jcr.Node;
 import javax.jcr.Property;
@@ -85,7 +87,7 @@ public class SetValueConstraintViolationExceptionTest extends AbstractJCRTest {
             String nodeType = propDef.getDeclaringNodeType().getName();
             node = testRootNode.addNode(nodeName2, nodeType);
             prop = node.setProperty(propDef.getName(), valueSatisfied);
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (ConstraintViolationException e) {
             // implementation specific constraints do not allow to set up test environment
             throw new NotExecutableException("Not able to create required test items.");
@@ -155,7 +157,7 @@ public class SetValueConstraintViolationExceptionTest extends AbstractJCRTest {
             String nodeType = propDef.getDeclaringNodeType().getName();
             node = testRootNode.addNode(nodeName2, nodeType);
             prop = node.setProperty(propDef.getName(), valueSatisfied);
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (ConstraintViolationException e) {
             // implementation specific constraints do not allow to set up test environment
             throw new NotExecutableException("Not able to create required test items.");
@@ -222,7 +224,7 @@ public class SetValueConstraintViolationExceptionTest extends AbstractJCRTest {
             String nodeType = propDef.getDeclaringNodeType().getName();
             node = testRootNode.addNode(nodeName2, nodeType);
             prop = node.setProperty(propDef.getName(), valueSatisfied);
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (ConstraintViolationException e) {
             // implementation specific constraints do not allow to set up test environment
             throw new NotExecutableException("Not able to create required test items.");
@@ -289,7 +291,7 @@ public class SetValueConstraintViolationExceptionTest extends AbstractJCRTest {
             String nodeType = propDef.getDeclaringNodeType().getName();
             node = testRootNode.addNode(nodeName2, nodeType);
             prop = node.setProperty(propDef.getName(), valueSatisfied);
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (ConstraintViolationException e) {
             // implementation specific constraints do not allow to set up test environment
             throw new NotExecutableException("Not able to create required test items.");
@@ -356,7 +358,7 @@ public class SetValueConstraintViolationExceptionTest extends AbstractJCRTest {
             String nodeType = propDef.getDeclaringNodeType().getName();
             node = testRootNode.addNode(nodeName2, nodeType);
             prop = node.setProperty(propDef.getName(), valueSatisfied);
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (ConstraintViolationException e) {
             // implementation specific constraints do not allow to set up test environment
             throw new NotExecutableException("Not able to create required test items.");
@@ -403,26 +405,32 @@ public class SetValueConstraintViolationExceptionTest extends AbstractJCRTest {
                     "testable value constraints has been found");
         }
 
-        String constraints[] = propDef.getValueConstraints();
-        String nodeTypeSatisfied = constraints[0];
+        String valueConstraints[] = propDef.getValueConstraints();
+        if (valueConstraints == null || valueConstraints.length == 0) {
+            throw new NotExecutableException("No reference property def with "
+                    + "testable value constraints has been found");
+        }
+        List<String> constraints = Arrays.asList(valueConstraints);
+        String nodeTypeSatisfied = constraints.get(0);
         String nodeTypeNotSatisfied = null;
 
         NodeTypeManager manager = superuser.getWorkspace().getNodeTypeManager();
         NodeTypeIterator types = manager.getAllNodeTypes();
 
         // find a NodeType which is not satisfying the constraints
-        findNodeTypeNotSatisfied:
-            while (types.hasNext()) {
-                NodeType type = types.nextNodeType();
-                String name = type.getName();
-                for (int i = 0; i < constraints.length; i++) {
-                    if (name.equals(constraints[i]) || ntFrozenNode.equals(name)) {
-                        continue findNodeTypeNotSatisfied;
-                    }
-                    nodeTypeNotSatisfied = name;
-                    break findNodeTypeNotSatisfied;
-                }
+        while (types.hasNext()) {
+            NodeType type = types.nextNodeType();
+            String name = type.getName();
+            if (constraints.contains(name) || ntFrozenNode.equals(name)) {
+                continue;
+            }
+            if (type.getChildNodeDefinitions() != null
+                    && type.getChildNodeDefinitions().length > 0) {
+                continue;
             }
+            nodeTypeNotSatisfied = name;
+            break;
+        }
 
         if (nodeTypeNotSatisfied == null) {
             throw new NotExecutableException("No reference property def with " +
@@ -448,10 +456,10 @@ public class SetValueConstraintViolationExceptionTest extends AbstractJCRTest {
             ensureMixinType(nodeNotSatisfied, mixReferenceable);
 
             // some implementations may require a save after addMixin()
-            testRootNode.save();
+            testRootNode.getSession().save();
 
             prop = node.setProperty(propDef.getName(), nodeSatisfied);
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (ConstraintViolationException e) {
             // implementation specific constraints do not allow to set up test environment
             throw new NotExecutableException("Not able to create required test items.");
@@ -518,7 +526,7 @@ public class SetValueConstraintViolationExceptionTest extends AbstractJCRTest {
             String nodeType = propDef.getDeclaringNodeType().getName();
             node = testRootNode.addNode(nodeName2, nodeType);
             prop = node.setProperty(propDef.getName(), new Value[]{valueSatisfied});
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (ConstraintViolationException e) {
             // implementation specific constraints do not allow to set up test environment
             throw new NotExecutableException("Not able to create required test items.");
@@ -573,7 +581,7 @@ public class SetValueConstraintViolationExceptionTest extends AbstractJCRTest {
             String nodeType = propDef.getDeclaringNodeType().getName();
             node = testRootNode.addNode(nodeName2, nodeType);
             prop = node.setProperty(propDef.getName(), new Value[]{valueSatisfied});
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (ConstraintViolationException e) {
             // implementation specific constraints do not allow to set up test environment
             throw new NotExecutableException("Not able to create required test items.");
@@ -629,7 +637,7 @@ public class SetValueConstraintViolationExceptionTest extends AbstractJCRTest {
             String nodeType = propDef.getDeclaringNodeType().getName();
             node = testRootNode.addNode(nodeName2, nodeType);
             prop = node.setProperty(propDef.getName(), new Value[]{valueSatisfied});
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (ConstraintViolationException e) {
             // implementation specific constraints do not allow to set up test environment
             throw new NotExecutableException("Not able to create required test items.");
@@ -684,7 +692,7 @@ public class SetValueConstraintViolationExceptionTest extends AbstractJCRTest {
             String nodeType = propDef.getDeclaringNodeType().getName();
             node = testRootNode.addNode(nodeName2, nodeType);
             prop = node.setProperty(propDef.getName(), new Value[]{valueSatisfied});
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (ConstraintViolationException e) {
             // implementation specific constraints do not allow to set up test environment
             throw new NotExecutableException("Not able to create required test items.");
@@ -740,7 +748,7 @@ public class SetValueConstraintViolationExceptionTest extends AbstractJCRTest {
             String nodeType = propDef.getDeclaringNodeType().getName();
             node = testRootNode.addNode(nodeName2, nodeType);
             prop = node.setProperty(propDef.getName(), new Value[]{valueSatisfied});
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (ConstraintViolationException e) {
             // implementation specific constraints do not allow to set up test environment
             throw new NotExecutableException("Not able to create required test items.");
@@ -775,29 +783,35 @@ public class SetValueConstraintViolationExceptionTest extends AbstractJCRTest {
                     "testable value constraints has been found");
         }
 
-        String constraints[] = propDef.getValueConstraints();
-        String nodeTypeSatisfied = constraints[0];
+        String valueConstraints[] = propDef.getValueConstraints();
+        if (valueConstraints == null || valueConstraints.length == 0) {
+            throw new NotExecutableException("No reference property def with "
+                    + "testable value constraints has been found");
+        }
+        List<String> constraints = Arrays.asList(valueConstraints);
+        String nodeTypeSatisfied = constraints.get(0);
         String nodeTypeNotSatisfied = null;
 
         NodeTypeManager manager = superuser.getWorkspace().getNodeTypeManager();
         NodeTypeIterator types = manager.getAllNodeTypes();
 
         // find a NodeType which is not satisfying the constraints
-        findNodeTypeNotSatisfied:
-            while (types.hasNext()) {
-                NodeType type = types.nextNodeType();
-                String name = type.getName();
-                for (int i = 0; i < constraints.length; i++) {
-                    if (name.equals(constraints[i]) || ntFrozenNode.equals(name)) {
-                        continue findNodeTypeNotSatisfied;
-                    }
-                    nodeTypeNotSatisfied = name;
-                    break findNodeTypeNotSatisfied;
-                }
+        while (types.hasNext()) {
+            NodeType type = types.nextNodeType();
+            String name = type.getName();
+            if (constraints.contains(name) || ntFrozenNode.equals(name)) {
+                continue;
             }
+            if (type.getChildNodeDefinitions() != null
+                    && type.getChildNodeDefinitions().length > 0) {
+                continue;
+            }
+            nodeTypeNotSatisfied = name;
+            break;
+        }
 
         if (nodeTypeNotSatisfied == null) {
-            throw new NotExecutableException("No multiple reference property def with " +
+            throw new NotExecutableException("No reference property def with " +
                     "testable value constraints has been found");
         }
 
@@ -820,11 +834,11 @@ public class SetValueConstraintViolationExceptionTest extends AbstractJCRTest {
             ensureMixinType(nodeNotSatisfied, mixReferenceable);
 
             // some implementations may require a save after addMixin()
-            testRootNode.save();
+            testRootNode.getSession().save();
 
             Value valueSatisfied = superuser.getValueFactory().createValue(nodeSatisfied);
             prop = node.setProperty(propDef.getName(), new Value[]{valueSatisfied});
-            testRootNode.save();
+            testRootNode.getSession().save();
         } catch (ConstraintViolationException e) {
             // implementation specific constraints do not allow to set up test environment
             throw new NotExecutableException("Not able to create required test items.");
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueInputStreamTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueInputStreamTest.java
index 0fd37c2..c786408 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueInputStreamTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueInputStreamTest.java
@@ -17,17 +17,13 @@
 package org.apache.jackrabbit.test.api;
 
 import org.apache.jackrabbit.test.AbstractJCRTest;
-import org.apache.jackrabbit.test.NotExecutableException;
 import org.apache.jackrabbit.test.api.util.InputStreamWrapper;
 
-import javax.jcr.Binary;
 import javax.jcr.Node;
 import javax.jcr.Property;
 import javax.jcr.RepositoryException;
-import javax.jcr.Value;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
-import java.io.InputStream;
 
 /**
  * Tests the {@link javax.jcr.Property#setValue(java.io.InputStream)} method.
@@ -72,7 +68,7 @@ public class SetValueInputStreamTest extends AbstractJCRTest {
 
         // create a new node under the testRootNode
         node = testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // special case for repositories that do allow binary property
         // values, but only on jcr:content/jcr:data
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueStringTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueStringTest.java
index 4a4cc46..46ee54b 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueStringTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueStringTest.java
@@ -106,7 +106,7 @@ public class SetValueStringTest extends AbstractJCRTest {
      */
     public void testValueParent() throws RepositoryException {
         property1.setValue(sv2);
-        testRootNode.save();
+        testRootNode.getSession().save();
         assertEquals("Value node property not saved", sv2, property1.getValue());
     }
 
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueValueFormatExceptionTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueValueFormatExceptionTest.java
index fdd6ca6..0b23fb9 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueValueFormatExceptionTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueValueFormatExceptionTest.java
@@ -217,7 +217,7 @@ public class SetValueValueFormatExceptionTest extends AbstractJCRTest {
             }
 
             // some implementations may require a save after addMixin()
-            testRootNode.save();
+            testRootNode.getSession().save();
 
             // make sure the node is now referenceable
             assertTrue("test node should be mix:referenceable", referenceableNode.isNodeType(mixReferenceable));
@@ -271,7 +271,7 @@ public class SetValueValueFormatExceptionTest extends AbstractJCRTest {
             } else {
                 n.addMixin(mixReferenceable);
                 // some implementations may require a save after addMixin()
-                testRootNode.save();
+                testRootNode.getSession().save();
             }
         }
 
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueVersionExceptionTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueVersionExceptionTest.java
index 395cda1..0579a6e 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueVersionExceptionTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/SetValueVersionExceptionTest.java
@@ -50,8 +50,10 @@ public class SetValueVersionExceptionTest extends AbstractJCRTest {
     private Property property;
     private Property multiProperty;
 
-    private Value value;
-    private Value values[];
+    private Value initialValue;
+    private Value[] initialValues;
+    private Value modifiedValue;
+    private Value[] modifiedValues;
 
     /**
      * Sets up the fixture for the test cases.
@@ -60,8 +62,10 @@ public class SetValueVersionExceptionTest extends AbstractJCRTest {
         super.setUp();
         session = getHelper().getReadOnlySession();
 
-        value = session.getValueFactory().createValue("abc");
-        values = new Value[] {value};
+        initialValue = session.getValueFactory().createValue("abc");
+        modifiedValue = session.getValueFactory().createValue("def");
+        initialValues = new Value[] {initialValue};
+        modifiedValues = new Value[] {initialValue, modifiedValue};
 
         if (!isSupported(Repository.OPTION_VERSIONING_SUPPORTED)) {
             throw new NotExecutableException("Versioning is not supported.");
@@ -72,10 +76,10 @@ public class SetValueVersionExceptionTest extends AbstractJCRTest {
         // or try to make it versionable if it is not
         ensureMixinType(node, mixVersionable);
 
-        property = node.setProperty(propertyName1, value);
-        multiProperty = node.setProperty(propertyName2, values);
+        property = node.setProperty(propertyName1, initialValue);
+        multiProperty = node.setProperty(propertyName2, initialValues);
 
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         node.checkin();
     }
@@ -85,6 +89,7 @@ public class SetValueVersionExceptionTest extends AbstractJCRTest {
      */
     protected void tearDown() throws Exception {
         try {
+            superuser.refresh(false);
             node.checkout();
         } finally {
             if (session != null) {
@@ -94,8 +99,10 @@ public class SetValueVersionExceptionTest extends AbstractJCRTest {
             node = null;
             property = null;
             multiProperty = null;
-            value = null;
-            values = null;
+            initialValue = null;
+            initialValues = null;
+            modifiedValue = null;
+            modifiedValues = null;
             super.tearDown();
         }
     }
@@ -106,7 +113,7 @@ public class SetValueVersionExceptionTest extends AbstractJCRTest {
      */
     public void testValue() throws RepositoryException {
         try {
-            property.setValue(value);
+            property.setValue(modifiedValue);
             node.save();
             fail("Property.setValue(Value) must throw a VersionException " +
                  "immediately or on save if the parent node of this property " +
@@ -123,7 +130,7 @@ public class SetValueVersionExceptionTest extends AbstractJCRTest {
      */
     public void testValueArray() throws RepositoryException {
         try {
-            multiProperty.setValue(values);
+            multiProperty.setValue(modifiedValues);
             node.save();
             fail("Property.setValue(Value[]) must throw a VersionException " +
                  "immediately or on save if the parent node of this property " +
@@ -140,7 +147,7 @@ public class SetValueVersionExceptionTest extends AbstractJCRTest {
      */
     public void testString() throws RepositoryException {
         try {
-            property.setValue("abc");
+            property.setValue(modifiedValue.getString());
             node.save();
             fail("Property.setValue(String) must throw a VersionException " +
                  "immediately or on save if the parent node of this property " +
@@ -157,7 +164,7 @@ public class SetValueVersionExceptionTest extends AbstractJCRTest {
      */
     public void testStringArray() throws RepositoryException {
         try {
-            String values[] = new String[] {"abc"};
+            String values[] = new String[0];
             multiProperty.setValue(values);
             node.save();
             fail("Property.setValue(String[]) must throw a VersionException " +
@@ -277,7 +284,7 @@ public class SetValueVersionExceptionTest extends AbstractJCRTest {
         ensureMixinType(referenceableNode, mixReferenceable);
 
         // implementation specific if mixin takes effect immediately or upon save
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         String refPropName = getProperty("propertyname3");
         String nodeType = getProperty("nodetype2");
@@ -291,12 +298,12 @@ public class SetValueVersionExceptionTest extends AbstractJCRTest {
         ensureCanSetProperty(node, refPropName, node.getSession().getValueFactory().createValue(referenceableNode));
 
         Property property = node.setProperty(refPropName, referenceableNode);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         node.checkin();
 
         try {
-            property.setValue(referenceableNode);
+            property.setValue(node);
             node.save();
             fail("Property.setValue(Node) must throw a VersionException " +
                  "immediately or on save if the parent node of this property " +
@@ -306,6 +313,7 @@ public class SetValueVersionExceptionTest extends AbstractJCRTest {
             // success
         }
 
+        superuser.refresh(false);
         node.checkout();
     }
 }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ShareableNodeTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ShareableNodeTest.java
index 786f4d6..08ce8fe 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ShareableNodeTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ShareableNodeTest.java
@@ -75,7 +75,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
         a2.addNode("b");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -105,7 +105,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -135,7 +135,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -152,8 +152,9 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node b2 = shared[1];
 
         // verify paths of nodes b1/b2 in shared set
-        assertEquals("/testroot/a1/b1", b1.getPath());
-        assertEquals("/testroot/a2/b2", b2.getPath());
+        String testRootNodePath = testRootNode.getPath();
+        assertEquals(testRootNodePath + "/a1/b1", b1.getPath());
+        assertEquals(testRootNodePath + "/a2/b2", b2.getPath());
     }
 
     /**
@@ -165,7 +166,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -194,7 +195,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -225,7 +226,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -255,7 +256,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -278,7 +279,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         // setup parent node and first child
         Node a = testRootNode.addNode("a");
         Node b = a.addNode("b");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         ensureMixinType(b, mixShareable);
         b.save();
@@ -292,7 +293,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // make b1 shareable
         ensureMixinType(b1, mixShareable);
@@ -336,7 +337,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -370,7 +371,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -383,7 +384,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
 
         // remove shared set
         b1.removeSharedSet();
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // verify neither a1 nor a2 contain any more children
         assertFalse(a1.hasNodes());
@@ -400,7 +401,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -432,7 +433,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -462,7 +463,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -498,7 +499,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = s.addNode("a1");
         Node a2 = s.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -537,7 +538,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         // setup parent nodes and first child
         Node a1 = testRootNode.addNode("a1");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -563,7 +564,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -599,7 +600,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -643,7 +644,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = p.addNode("a1");
         Node a2 = p.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -669,7 +670,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
 
         // delete p and save
         p.remove();
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // and import again underneath test root
         InputStream in = new FileInputStream(tmpFile);
@@ -697,7 +698,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a2 = testRootNode.addNode("a2");
         Node a3 = testRootNode.addNode("a3");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -754,7 +755,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a2 = testRootNode.addNode("a2");
         Node a3 = testRootNode.addNode("a3");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -811,7 +812,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a2 = testRootNode.addNode("a2");
         Node a3 = testRootNode.addNode("a3");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -869,7 +870,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a2 = testRootNode.addNode("a2");
         Node a3 = testRootNode.addNode("a3");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -924,7 +925,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         ensureMixinType(a1, mixLockable);
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -973,7 +974,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // make b1 shareable
         ensureMixinType(b1, mixShareable);
@@ -1025,7 +1026,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         Workspace workspace = b1.getSession().getWorkspace();
 
@@ -1056,7 +1057,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -1078,26 +1079,75 @@ public class ShareableNodeTest extends AbstractJCRTest {
     }
 
     /**
-     * Remove mix:shareable from a shareable node. This is unsupported in
-     * Jackrabbit (6.13.22).
+     * Remove mix:shareable from a shareable node.
      */
     public void testRemoveMixin() throws Exception {
         // setup parent node and first child
         Node a = testRootNode.addNode("a");
         Node b = a.addNode("b");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b, mixShareable);
-        b.save();
+        b.getSession().save();
 
+        // Removing the mixin will either succeed or will fail with a
+        // ConstraintViolationException
+        // (per Section 14.15 of JSR-283 specification)
         try {
             // remove mixin
             b.removeMixin(mixShareable);
-            b.save();
-            fail("Removing mix:shareable should fail.");
+            b.getSession().save();
+            // If this happens, then b shouldn't be shareable anymore ...
+            assertFalse(b.isNodeType(mixShareable));
+        } catch (ConstraintViolationException e) {
+            // one possible outcome if removing 'mix:shareable' isn't supported
         } catch (UnsupportedRepositoryOperationException e) {
-            // expected
+            // also possible if the implementation doesn't support this
+            // capability
+        }
+    }
+
+    /**
+     * Remove mix:shareable from a shareable node that has 2 nodes in the shared set. 
+     */
+    public void testRemoveMixinFromSharedNode() throws Exception {
+        // setup parent nodes and first child
+        Node a1 = testRootNode.addNode("a1");
+        Node a2 = testRootNode.addNode("a2");
+        Node b1 = a1.addNode("b1");
+        testRootNode.getSession().save();
+
+        // add mixin
+        ensureMixinType(b1, mixShareable);
+        b1.getSession().save();
+
+        // clone
+        Workspace workspace = b1.getSession().getWorkspace();
+        workspace.clone(workspace.getName(), b1.getPath(), a2.getPath() + "/b2", false);
+
+        Node[] shared = getSharedSet(b1);
+        assertEquals(2, shared.length);
+        b1 = shared[0];
+        Node b2 = shared[1];
+        assertTrue(b2.isSame(b1));
+
+        // Removing the mixin will either succeed or will fail with a
+        // ConstraintViolationException
+        // (per Section 14.15 of JSR-283 specification)
+        try {
+            // remove mixin
+            b1.removeMixin(mixShareable);
+            b1.getSession().save();
+            // If this happens, then b1 shouldn't be shareable anymore
+            // ...
+            assertFalse(b1.isNodeType(mixShareable));
+            assertFalse(b2.isSame(b1));
+        } catch (ConstraintViolationException e) {
+            // one possible outcome if removing 'mix:shareable' isn't supported
+        } catch (UnsupportedRepositoryOperationException e) {
+            // also possible if the implementation doesn't support this
+            // capability
         }
     }
 
@@ -1110,7 +1160,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -1149,7 +1199,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         // setup parent nodes and first child
         Node a = testRootNode.addNode("a");
         Node b1 = a.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -1168,57 +1218,52 @@ public class ShareableNodeTest extends AbstractJCRTest {
     }
 
     /**
-     * Move a node in a shared set. This is unsupported in Jackrabbit.
+     * Move a node in a shared set.
      */
     public void testMoveShareableNode() throws Exception {
-        // setup parent nodes and first childs
+        // setup parent nodes and first children
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b = a1.addNode("b");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b, mixShareable);
-        b.save();
+        b.getSession().save();
 
         // move
         Workspace workspace = b.getSession().getWorkspace();
 
-        try {
-            // move shareable node
-            workspace.move(b.getPath(), a2.getPath() + "/b");
-            fail("Moving a mix:shareable should fail.");
-        } catch (UnsupportedRepositoryOperationException e) {
-            // expected
-        }
+        // move shareable node
+        String newPath = a2.getPath() + "/b";
+        workspace.move(b.getPath(), newPath);
+        // move was performed using the workspace, so refresh the session
+        b.getSession().refresh(false);
+        assertEquals(newPath, b.getPath());
     }
 
     /**
-     * Transiently move a node in a shared set. This is unsupported in
-     * Jackrabbit.
+     * Transiently move a node in a shared set.
      */
     public void testTransientMoveShareableNode() throws Exception {
-        // setup parent nodes and first childs
+        // setup parent nodes and first children
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b = a1.addNode("b");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b, mixShareable);
-        b.save();
+        b.getSession().save();
 
         // move
         Session session = superuser;
 
-        try {
-            // move shareable node
-            session.move(b.getPath(), a2.getPath() + "/b");
-            session.save();
-            fail("Moving a mix:shareable should fail.");
-        } catch (UnsupportedRepositoryOperationException e) {
-            // expected
-        }
+        // move shareable node
+        String newPath = a2.getPath() + "/b";
+        session.move(b.getPath(), newPath);
+        session.save();
+        assertEquals(newPath, b.getPath());
     }
 
     //----------------------------------------------------- implementation tests
@@ -1232,7 +1277,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -1267,7 +1312,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -1305,7 +1350,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -1339,7 +1384,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         Node a1 = testRootNode.addNode("a1");
         Node a2 = testRootNode.addNode("a2");
         Node b1 = a1.addNode("b1");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b1, mixShareable);
@@ -1382,7 +1427,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
             parents[i] = testRootNode.addNode("a" + (i + 1));
         }
         Node b = parents[0].addNode("b");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // add mixin
         ensureMixinType(b, mixShareable);
@@ -1400,7 +1445,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
         for (int i = 0; i < parents.length; i++) {
             parents[i].remove();
         }
-        testRootNode.save();
+        testRootNode.getSession().save();
     }
 
     /**
@@ -1411,7 +1456,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
        Node a2 = a1.addNode("a2");
        Node b1 = a1.addNode("b1");
        ensureMixinType(b1, mixShareable);
-       testRootNode.save();
+       testRootNode.getSession().save();
 
        //now we have a shareable node N with path a1/b1
 
@@ -1446,7 +1491,7 @@ public class ShareableNodeTest extends AbstractJCRTest {
      * @return node array
      */
     private static Node[] toArray(NodeIterator iter) {
-        ArrayList list = new ArrayList();
+        List<Node> list = new ArrayList<Node>();
 
         while (iter.hasNext()) {
             list.add(iter.nextNode());
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/TreeComparator.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/TreeComparator.java
index e5d4a22..590819a 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/TreeComparator.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/TreeComparator.java
@@ -196,7 +196,7 @@ class TreeComparator extends Assert {
             try {
                 n = nt.addNode(name);
                 n.addMixin(t.getName());
-                // try saving, because some exceptions are trown only at save time
+                // try saving, because some exceptions are thrown only at save time
                 session.save();
             } catch (RepositoryException e) {
                 sc.log("Cannot create node with mixin node type: " + e);
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ValueFactoryTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ValueFactoryTest.java
index 34c36a6..3c185a5 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ValueFactoryTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/ValueFactoryTest.java
@@ -117,7 +117,7 @@ public class ValueFactoryTest extends AbstractJCRTest {
         if (n1.canAddMixin(mixReferenceable)) {
             n1.addMixin(mixReferenceable);
             // make sure jcr:uuid is available
-            testRootNode.save();
+            testRootNode.getSession().save();
             return n1;
         }
         else {
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/WorkspaceCopySameNameSibsTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/WorkspaceCopySameNameSibsTest.java
index d34cfbc..224b768 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/WorkspaceCopySameNameSibsTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/WorkspaceCopySameNameSibsTest.java
@@ -78,7 +78,7 @@ public class WorkspaceCopySameNameSibsTest extends AbstractWorkspaceSameNameSibs
     public void testCopyNodesNodeExistsAtDestPath() throws RepositoryException {
         // create a parent node where allowSameNameSiblings are set to false
         Node snsfNode = testRootNode.addNode(nodeName3, sameNameSibsFalseNodeType.getName());
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         String dstAbsPath = snsfNode.getPath() + "/" + node1.getName();
         workspace.copy(node1.getPath(), dstAbsPath);
@@ -104,7 +104,7 @@ public class WorkspaceCopySameNameSibsTest extends AbstractWorkspaceSameNameSibs
     public void testCopyNodesNodeExistsAtDestPath2() throws RepositoryException {
         // create a parent node where allowSameNameSiblings are set to true
         Node snsfNode = testRootNode.addNode(nodeName3, sameNameSibsTrueNodeType.getName());
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         String dstAbsPath = snsfNode.getPath() + "/" + node1.getName();
         workspace.copy(node1.getPath(), dstAbsPath);
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/WorkspaceCopyTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/WorkspaceCopyTest.java
index bf47ae1..f51efaa 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/WorkspaceCopyTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/WorkspaceCopyTest.java
@@ -77,7 +77,7 @@ public class WorkspaceCopyTest extends AbstractWorkspaceCopyTest {
         // if parent node is nt:base then no sub nodes can be created
         String nodetype = testNodeTypeNoChildren == null ? ntBase : testNodeTypeNoChildren;
         Node subNodesNotAllowedNode = testRootNode.addNode(nodeName3, nodetype);
-        testRootNode.save();
+        testRootNode.getSession().save();
         try {
             String dstAbsPath = subNodesNotAllowedNode.getPath() + "/" + node2.getName();
             workspace.copy(node2.getPath(), dstAbsPath);
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/WorkspaceMoveSameNameSibsTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/WorkspaceMoveSameNameSibsTest.java
index bbb646f..6c3b7e0 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/WorkspaceMoveSameNameSibsTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/WorkspaceMoveSameNameSibsTest.java
@@ -53,7 +53,7 @@ public class WorkspaceMoveSameNameSibsTest extends AbstractWorkspaceSameNameSibs
 
         // create a new node to move nodes
         Node newNode = testRootNode.addNode(nodeName2, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // copy node three times below a node and check the order
         for (int i = 0; i < orderList.length; i++) {
@@ -82,7 +82,7 @@ public class WorkspaceMoveSameNameSibsTest extends AbstractWorkspaceSameNameSibs
     public void testMoveNodesNodeExistsAtDestPath() throws RepositoryException {
         // create a parent node where allowSameNameSiblings are set to false
         Node snsfNode = testRootNode.addNode(nodeName3, sameNameSibsFalseNodeType.getName());
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         String dstAbsPath = snsfNode.getPath() + "/" + node1.getName();
         workspace.copy(node1.getPath(), dstAbsPath);
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/WorkspaceMoveTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/WorkspaceMoveTest.java
index 62a4870..0a94e58 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/WorkspaceMoveTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/WorkspaceMoveTest.java
@@ -76,7 +76,7 @@ public class WorkspaceMoveTest extends AbstractWorkspaceCopyTest {
         // if parent node is nt:base then no sub nodes can be created
         String nodetype = testNodeTypeNoChildren == null ? ntBase : testNodeTypeNoChildren;
         Node subNodesNotAllowedNode = testRootNode.addNode(nodeName3, nodetype);
-        testRootNode.save();
+        testRootNode.getSession().save();
         try {
             String dstAbsPath = subNodesNotAllowedNode.getPath() + "/" + node2.getName();
             workspace.move(node2.getPath(), dstAbsPath);
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/AbstractLockTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/AbstractLockTest.java
index 948aeca..deb0587 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/AbstractLockTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/AbstractLockTest.java
@@ -51,7 +51,7 @@ public abstract class AbstractLockTest extends AbstractJCRTest {
         lockedNode = testRootNode.addNode(nodeName1, testNodeType);
         ensureMixinType(lockedNode, mixLockable);
         childNode = lockedNode.addNode(nodeName2, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         lockMgr = getLockManager(testRootNode.getSession());
         lock = lockMgr.lock(lockedNode.getPath(), isDeep(), isSessionScoped(), getTimeoutHint(), getLockOwner());
@@ -149,7 +149,7 @@ public abstract class AbstractLockTest extends AbstractJCRTest {
     /**
      * Test {@link javax.jcr.lock.Lock#getNode()}.
      *
-     * @throws RepositoryException If an execption occurs.
+     * @throws RepositoryException If an exception occurs.
      */
     public void testLockHoldingNode() throws RepositoryException {
         assertTrue("Lock.getNode() must be lockholding node.", lock.getNode().isSame(lockedNode));
@@ -158,7 +158,7 @@ public abstract class AbstractLockTest extends AbstractJCRTest {
     /**
      * Test {@link LockManager#isLocked(String)} and {@link javax.jcr.Node#isLocked()}.
      *
-     * @throws RepositoryException If an execption occurs.
+     * @throws RepositoryException If an exception occurs.
      */
     public void testNodeIsLocked() throws RepositoryException {
         assertTrue("Node must be locked after lock creation.", lockedNode.isLocked());
@@ -168,7 +168,7 @@ public abstract class AbstractLockTest extends AbstractJCRTest {
     /**
      * Test {@link LockManager#holdsLock(String)} and {@link javax.jcr.Node#holdsLock()}. 
      *
-     * @throws RepositoryException If an execption occurs.
+     * @throws RepositoryException If an exception occurs.
      */
     public void testNodeHoldsLocked() throws RepositoryException {
         assertTrue("Node must hold lock after lock creation.", lockedNode.holdsLock());
@@ -201,7 +201,7 @@ public abstract class AbstractLockTest extends AbstractJCRTest {
     /**
      * Test {@link javax.jcr.lock.Lock#isLockOwningSession()}
      *
-     * @throws RepositoryException If an execption occurs.
+     * @throws RepositoryException If an exception occurs.
      */
     public void testIsLockOwningSession() throws RepositoryException {
         assertTrue("Session must be lock owner", lock.isLockOwningSession());
@@ -265,9 +265,11 @@ public abstract class AbstractLockTest extends AbstractJCRTest {
         // only test if timeout hint was respected.
         long remaining = lock.getSecondsRemaining();
         if (remaining <= hint) {
-            try {
-                wait(remaining * 4000); // wait four time as long to be safe
-            } catch (InterruptedException ignore) {
+            if (remaining > 0) {
+                try {
+                    wait(remaining * 4000); // wait four time as long to be safe
+                } catch (InterruptedException ignore) {
+                }
             }
             long secs = lock.getSecondsRemaining();
             assertTrue(
@@ -286,6 +288,24 @@ public abstract class AbstractLockTest extends AbstractJCRTest {
     }
 
     /**
+     * Test expiration of the lock
+     */
+    public synchronized void testOwnerHint()
+            throws RepositoryException, NotExecutableException {
+        lockedNode.unlock();
+
+        lock = lockMgr.lock(lockedNode.getPath(), isDeep(), isSessionScoped(), Long.MAX_VALUE, "test");
+
+        String owner = lock.getLockOwner();
+        if (!"test".equals(lock.getLockOwner())) {
+            throw new NotExecutableException();
+        } else {
+            assertTrue(lockedNode.hasProperty(Property.JCR_LOCK_OWNER));
+            assertEquals("test", lockedNode.getProperty(Property.JCR_LOCK_OWNER).getString());
+        }
+    }
+
+    /**
      * Test if Lock is properly released.
      * 
      * @throws RepositoryException
@@ -409,7 +429,7 @@ public abstract class AbstractLockTest extends AbstractJCRTest {
             lockedNode.removeMixin(mixLockable);
             lockedNode.save();
 
-            // the mixin got removed -> the lock should implicitely be released
+            // the mixin got removed -> the lock should implicitly be released
             // as well in order not to have inconsistencies
             String msg = "Lock should have been released.";
             assertFalse(msg, lock.isLive());
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/DeepLockTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/DeepLockTest.java
index 2d37316..13b6982 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/DeepLockTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/DeepLockTest.java
@@ -17,8 +17,6 @@
 package org.apache.jackrabbit.test.api.lock;
 
 import org.apache.jackrabbit.test.NotExecutableException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
@@ -28,8 +26,6 @@ import javax.jcr.lock.LockException;
 /** <code>DeepLockTest</code>... */
 public class DeepLockTest extends AbstractLockTest {
 
-    private static Logger log = LoggerFactory.getLogger(DeepLockTest.class);
-
     protected boolean isSessionScoped() {
         return true;
     }
@@ -52,7 +48,7 @@ public class DeepLockTest extends AbstractLockTest {
     public void testParentChildDeepLock()
             throws RepositoryException, NotExecutableException {
         ensureMixinType(childNode, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // try to lock child node
         try {
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/LockManagerTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/LockManagerTest.java
index bf39ddb..16d0c6a 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/LockManagerTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/LockManagerTest.java
@@ -30,14 +30,10 @@ import javax.jcr.lock.LockManager;
 
 import org.apache.jackrabbit.test.AbstractJCRTest;
 import org.apache.jackrabbit.test.NotExecutableException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /** <code>LockManagerTest</code>... */
 public class LockManagerTest extends AbstractJCRTest {
 
-    private static Logger log = LoggerFactory.getLogger(LockManagerTest.class);
-
     protected LockManager lockMgr;
     protected Node testNode;
     protected String testPath;
@@ -51,7 +47,7 @@ public class LockManagerTest extends AbstractJCRTest {
         }
         
         testNode = testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         testPath = testNode.getPath();
 
         lockMgr = getLockManager(superuser);
@@ -243,7 +239,9 @@ public class LockManagerTest extends AbstractJCRTest {
         try {
             lockMgr.removeLockToken(ltoken);
 
-            assertNull("Lock token must not be exposed any more.", l.getLockToken());
+            String nlt = l.getLockToken();
+            assertTrue("freshly obtained lock token must either be null or the same as the one returned earlier",
+                    nlt == null || nlt.equals(ltoken));
         } finally {
             // make sure lock token is added even if test fail
             lockMgr.addLockToken(ltoken);
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/LockTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/LockTest.java
index 9231d11..9778bd5 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/LockTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/LockTest.java
@@ -50,7 +50,7 @@ public class LockTest extends AbstractJCRTest {
         // create new node
         Node n = testRootNode.addNode(nodeName1, testNodeType);
         ensureMixinType(n, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // lock node and get lock token
         Lock lock = n.lock(false, false);
@@ -64,8 +64,10 @@ public class LockTest extends AbstractJCRTest {
 
             // remove lock token
             superuser.removeLockToken(lockToken);
-            // assert: session must get a null lock token
-            assertNull("session must get a null lock token", lock.getLockToken());
+
+            String nlt = lock.getLockToken();
+            assertTrue("freshly obtained lock token must either be null or the same as the one returned earlier",
+                    nlt == null || nlt.equals(lockToken));
 
             // assert: session must still hold lock token
             assertFalse("session must not hold lock token",
@@ -106,7 +108,7 @@ public class LockTest extends AbstractJCRTest {
         // create new node and lock it
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         ensureMixinType(n1, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // lock node
         Lock lock = n1.lock(false, true);
@@ -146,7 +148,7 @@ public class LockTest extends AbstractJCRTest {
         ensureMixinType(n1, mixLockable);
         Node n1Sub = n1.addNode(nodeName1, testNodeType);
         ensureMixinType(n1Sub, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // lock node
         n1.lock(true, true);
@@ -170,7 +172,7 @@ public class LockTest extends AbstractJCRTest {
         // create new node and lock it
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         ensureMixinType(n1, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // lock node
         Lock lock = n1.lock(false, true);
@@ -196,7 +198,7 @@ public class LockTest extends AbstractJCRTest {
         // create new node and lock it
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         ensureMixinType(n1, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // lock node
         Lock lock = n1.lock(false, true);
@@ -217,7 +219,7 @@ public class LockTest extends AbstractJCRTest {
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         ensureMixinType(n1, mixLockable);
         Node n2 = n1.addNode(nodeName2, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // lock parent node
         n1.lock(false, true);
@@ -241,7 +243,7 @@ public class LockTest extends AbstractJCRTest {
         ensureMixinType(node, mixLockable);
         // try to make it versionable if it is not
         ensureMixinType(node, mixVersionable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         node.checkin();
 
@@ -271,7 +273,7 @@ public class LockTest extends AbstractJCRTest {
         ensureMixinType(n1, mixLockable);
         Node n2 = n1.addNode(nodeName2, testNodeType);
         ensureMixinType(n2, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // lock parent node
         n1.lock(false, true);
@@ -295,7 +297,7 @@ public class LockTest extends AbstractJCRTest {
         ensureMixinType(n1, mixLockable);
         Node n2 = n1.addNode(nodeName2, testNodeType);
         ensureMixinType(n2, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // lock child node
         n2.lock(false, true);
@@ -318,7 +320,7 @@ public class LockTest extends AbstractJCRTest {
         ensureMixinType(n1, mixLockable);
         Node n2 = testRootNode.addNode(nodeName2, testNodeType);
         ensureMixinType(n2, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // lock node 1 "undeeply"
         Lock lock1 = n1.lock(false, true);
@@ -343,7 +345,7 @@ public class LockTest extends AbstractJCRTest {
         ensureMixinType(n1, mixLockable);
         Node n2 = testRootNode.addNode(nodeName2, testNodeType);
         ensureMixinType(n2, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // lock node 1 session-scoped
         Lock lock1 = n1.lock(false, true);
@@ -367,7 +369,7 @@ public class LockTest extends AbstractJCRTest {
         // add node
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         ensureMixinType(n1, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // create new session
         Session otherSuperuser = getHelper().getSuperuserSession();
@@ -407,7 +409,7 @@ public class LockTest extends AbstractJCRTest {
         // add node
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         ensureMixinType(n1, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // create new session
         Session otherSuperuser = getHelper().getSuperuserSession();
@@ -427,8 +429,9 @@ public class LockTest extends AbstractJCRTest {
             otherSuperuser.removeLockToken(lockToken);
             superuser.addLockToken(lockToken);
 
-            // assert: user must get null token
-            assertNull("user must get null token", lock.getLockToken());
+            String nlt = lock.getLockToken();
+            assertTrue("freshly obtained lock token must either be null or the same as the one returned earlier",
+                    nlt == null || nlt.equals(lockToken));
 
             // assert: user must get non-null token
             assertNotNull("user must get non-null token",
@@ -448,7 +451,7 @@ public class LockTest extends AbstractJCRTest {
         // add node
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         ensureMixinType(n1, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // create new session
         Session otherSuperuser = getHelper().getSuperuserSession();
@@ -480,7 +483,7 @@ public class LockTest extends AbstractJCRTest {
         // create new node
         Node n = testRootNode.addNode(nodeName1, testNodeType);
         ensureMixinType(n, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // lock node and get lock token
         Lock lock = n.lock(false, true);
@@ -505,7 +508,7 @@ public class LockTest extends AbstractJCRTest {
         // create new node
         Node n = testRootNode.addNode(nodeName1, testNodeType);
         ensureMixinType(n, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // lock node and get lock token
         Lock lock = n.lock(false, true);
@@ -537,7 +540,7 @@ public class LockTest extends AbstractJCRTest {
         ensureMixinType(n1, mixLockable);
         Node n2 = n1.addNode(nodeName2, testNodeType);
         ensureMixinType(n2, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // deep lock parent node
         n1.lock(true, true);
@@ -561,7 +564,7 @@ public class LockTest extends AbstractJCRTest {
         Node testNode = testRootNode.addNode(nodeName1, testNodeType);
         ensureMixinType(testNode, mixLockable);
         ensureMixinType(testNode, mixVersionable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // lock and check-in
         try {
@@ -596,7 +599,7 @@ public class LockTest extends AbstractJCRTest {
 
         // move last node in front of first
         testRootNode.orderBefore(nodeName1 + "[3]", nodeName1 + "[1]");
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // assert: first node locked
         assertTrue("First child node locked",
@@ -619,7 +622,7 @@ public class LockTest extends AbstractJCRTest {
 
         // move first node to last
         testRootNode.orderBefore(nodeName1 + "[1]", null);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // assert: third node locked
         assertTrue("Third child node locked",
@@ -639,7 +642,7 @@ public class LockTest extends AbstractJCRTest {
         ensureMixinType(testNode1, mixLockable);
         Node testNode2 = testNode1.addNode(nodeName2, testNodeType);
         ensureMixinType(testNode2, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // lock child node
         testNode2.lock(false, true);
@@ -724,7 +727,7 @@ public class LockTest extends AbstractJCRTest {
             ensureMixinType(testNode, mixLockable);
             testNode = testRootNode.addNode(nodeName1, testNodeType);
             ensureMixinType(testNode, mixLockable);
-            testRootNode.save();
+            testRootNode.getSession().save();
             return testNode;
         }
         catch (ItemExistsException ex) {
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/OpenScopedLockTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/OpenScopedLockTest.java
index 795f8ba..16bab59 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/OpenScopedLockTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/OpenScopedLockTest.java
@@ -16,14 +16,9 @@
  */
 package org.apache.jackrabbit.test.api.lock;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 /** <code>OpenScopedLockTest</code>... */
 public class OpenScopedLockTest extends AbstractLockTest {
 
-    private static Logger log = LoggerFactory.getLogger(OpenScopedLockTest.class);
-
     protected boolean isSessionScoped() {
         return false;
     }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/SessionScopedLockTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/SessionScopedLockTest.java
index cb81259..647155e 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/SessionScopedLockTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/SessionScopedLockTest.java
@@ -16,8 +16,6 @@
  */
 package org.apache.jackrabbit.test.api.lock;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.apache.jackrabbit.test.NotExecutableException;
 
 import javax.jcr.RepositoryException;
@@ -30,8 +28,6 @@ import javax.jcr.lock.LockManager;
 /** <code>SessionScopedLockTest</code>... */
 public class SessionScopedLockTest extends AbstractLockTest {
 
-    private static Logger log = LoggerFactory.getLogger(SessionScopedLockTest.class);
-
     protected boolean isSessionScoped() {
         return true;
     }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/SetValueLockExceptionTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/SetValueLockExceptionTest.java
index 9e12651..5dd9d46 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/SetValueLockExceptionTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/lock/SetValueLockExceptionTest.java
@@ -114,7 +114,7 @@ public class SetValueLockExceptionTest extends AbstractJCRTest {
             testNode.setProperty(stringProp, stringValue);
             ensureCanSetProperty(testNode, multiStringProp, PropertyType.STRING, true);
             testNode.setProperty(multiStringProp, multiString);
-            testRootNode.save();
+            testRootNode.getSession().save();
         }
     }
 
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/nodetype/CanAddChildNodeCallWithNodeTypeTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/nodetype/CanAddChildNodeCallWithNodeTypeTest.java
index 10be03c..8ef5d38 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/nodetype/CanAddChildNodeCallWithNodeTypeTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/nodetype/CanAddChildNodeCallWithNodeTypeTest.java
@@ -218,18 +218,23 @@ public class CanAddChildNodeCallWithNodeTypeTest extends AbstractJCRTest {
     public void testResidualAndLegalType()
             throws NotExecutableException, RepositoryException {
 
-        NodeDefinition nodeDef = NodeTypeUtil.locateChildNodeDef(session, false, false, true);
-
-        if (nodeDef == null) {
-            throw new NotExecutableException("No testable residual child node def.");
+        String type = null;
+        NodeType nodeType = null;
+
+        for (NodeDefinition nodeDef : NodeTypeUtil.locateAllChildNodeDef(
+                session, false, false, true)) {
+            for (NodeType nt : nodeDef.getRequiredPrimaryTypes()) {
+                if (!nt.isAbstract()) {
+                    nodeType = nodeDef.getDeclaringNodeType();
+                    type = nt.getName();
+                }
+            }
         }
-
-        String type = nodeDef.getRequiredPrimaryTypes()[0].getName();
-        if (type.equals(ntBase)) {
-            // nt:base is abstract and can never be added, upgrade for check below
-            type = ntUnstructured;
+        if (nodeType == null || type == null) {
+            throw new NotExecutableException(
+                    "No testable residual child node def.");
         }
-        NodeType nodeType = nodeDef.getDeclaringNodeType();
+
         String undefinedName = NodeTypeUtil.getUndefinedChildNodeName(nodeType);
 
         assertTrue("NodeType.canAddChildNode(String childNodeName, String nodeTypeName) " +
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/nodetype/NodeTypeTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/nodetype/NodeTypeTest.java
index dca6dac..80f83a0 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/nodetype/NodeTypeTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/nodetype/NodeTypeTest.java
@@ -21,9 +21,6 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
 
-import org.apache.jackrabbit.test.AbstractJCRTest;
-import org.apache.jackrabbit.test.NotExecutableException;
-
 import javax.jcr.ItemNotFoundException;
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
@@ -35,6 +32,11 @@ import javax.jcr.nodetype.NodeTypeIterator;
 import javax.jcr.nodetype.NodeTypeManager;
 import javax.jcr.nodetype.PropertyDefinition;
 
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.jackrabbit.test.NotExecutableException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 /**
  * Tests if the node type hierarchy is correctly mapped to the methods
  * defined in {@link NodeType}.
@@ -46,6 +48,8 @@ import javax.jcr.nodetype.PropertyDefinition;
  */
 public class NodeTypeTest extends AbstractJCRTest {
 
+    private static Logger log = LoggerFactory.getLogger(NodeTypeTest.class);
+
     /**
      * The session we use for the tests
      */
@@ -291,6 +295,35 @@ public class NodeTypeTest extends AbstractJCRTest {
     }
 
     /**
+     * Like {@link #testIsNodeType()}, but using qualified names
+     */
+    public void testIsNodeTypeQName() throws RepositoryException {
+
+        // find a primary node type but not "nt:base"
+        NodeTypeIterator types = manager.getPrimaryNodeTypes();
+        while (types.hasNext()) {
+            NodeType type = types.nextNodeType();
+            String typename = type.getName();
+            String ns = session.getNamespaceURI(AbstractJCRTest.getPrefix(typename));
+            if (ns.length() != 0 && !ns.contains(":")) {
+                log.warn("Node type '" + typename + "' has invalid namespace '" + ns
+                        + "', thus skipping testIsNodeTypeQName() for this type");
+            } else {
+                String qn = AbstractJCRTest.getQualifiedName(session, typename);
+                assertTrue("isNodeType(String nodeTypeName) must return true if " + "NodeType is nodeTypeName",
+                        type.isNodeType(qn));
+            }
+            if (type.isMixin()) {
+                assertFalse("isNodeType(String nodeTypeName) must return " + "false if NodeType is not a subtype of "
+                        + "nodeTypeName", type.isNodeType(NodeType.NT_BASE));
+            } else {
+                assertTrue("isNodeType(String nodeTypeName) must return true if " + "NodeType is a subtype of nodeTypeName",
+                        type.isNodeType(NodeType.NT_BASE));
+            }
+        }
+    }
+
+    /**
      * Test if all property defs returned by getDeclatedPropertyDefs() are also
      * returned by getPropertyDefs(). All existing node types are tested.
      */
@@ -406,9 +439,22 @@ public class NodeTypeTest extends AbstractJCRTest {
 
         }
 
+        Node skippedFolder = null;
         NodeIterator nodes = node.getNodes();
         while (nodes.hasNext()) {
-            Node returnedNode = locateNodeWithPrimaryItem(nodes.nextNode());
+            Node testNode = nodes.nextNode();
+            if (testNode.getPath().equals("/jcr:system")) {
+                skippedFolder = testNode;
+            } else {
+                Node returnedNode = locateNodeWithPrimaryItem(testNode);
+                if (returnedNode != null) {
+                    return returnedNode;
+                }
+            }
+        }
+        // check jcr:system if we skipped it before
+        if (skippedFolder != null) {
+            Node returnedNode = locateNodeWithPrimaryItem(skippedFolder);
             if (returnedNode != null) {
                 return returnedNode;
             }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/nodetype/NodeTypeUtil.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/nodetype/NodeTypeUtil.java
index 582b93a..f1d561f 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/nodetype/NodeTypeUtil.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/nodetype/NodeTypeUtil.java
@@ -28,7 +28,10 @@ import javax.jcr.nodetype.NodeType;
 import javax.jcr.nodetype.NodeTypeIterator;
 import javax.jcr.nodetype.NodeTypeManager;
 import javax.jcr.nodetype.PropertyDefinition;
+
+import java.util.ArrayList;
 import java.util.Calendar;
+import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -147,6 +150,114 @@ public class NodeTypeUtil {
     }
 
     /**
+     * Locate all non-protected child node def declared by a non-abstract node type
+     * parsing all node types
+     *
+     * @param session                  the session to access the node types
+     * @param regardDefaultPrimaryType if true, the default primary type of the
+     *                                 returned <code>NodeDef</code> is
+     *                                 according to param <code>defaultPrimaryType</code>.
+     *                                 If false, the returned <code>NodeDef</code>
+     *                                 might have a default primary type or
+     *                                 not.
+     * @param defaultPrimaryType       if <code>regardDefaultPrimaryType</code>
+     *                                 is true: if true, the returned
+     *                                 <code>NodeDef</code> has a default
+     *                                 primary type, else not
+     * @param residual                 if true, the returned <code>NodeDef</code>
+     *                                 is of the residual name "*", else not
+     * @return
+     * @throws RepositoryException
+     */
+    public static List<NodeDefinition> locateAllChildNodeDef(Session session,
+                                                    boolean regardDefaultPrimaryType,
+                                                    boolean defaultPrimaryType,
+                                                    boolean residual)
+            throws RepositoryException {
+        List<NodeDefinition> nodeTypes = new ArrayList<NodeDefinition>();
+
+        NodeTypeManager manager = session.getWorkspace().getNodeTypeManager();
+        NodeTypeIterator types = manager.getAllNodeTypes();
+
+        boolean skip = false;
+
+        while (types.hasNext()) {
+            NodeType type = types.nextNodeType();
+
+            // node types with more than one residual child node definition
+            // will cause trouble in test cases. the implementation
+            // might pick another definition than the definition returned by
+            // this method, when a child node is set.
+            NodeDefinition[] childDefs = type.getChildNodeDefinitions();
+            int residuals = 0;
+            for (int i = 0; i < childDefs.length; i++) {
+                if (childDefs[i].getName().equals("*")) {
+                    residuals++;
+                }
+            }
+            if (residuals > 1) {
+                // more than one residual, not suitable for tests
+                continue;
+            }
+
+            NodeDefinition nodeDefs[] = type.getDeclaredChildNodeDefinitions();
+
+            for (int i = 0; i < nodeDefs.length; i++) {
+                NodeDefinition nodeDef = nodeDefs[i];
+
+                if (nodeDef.getDeclaringNodeType().isAbstract()) {
+                    continue;
+                }
+
+                if (nodeDef.isProtected()) {
+                    continue;
+                }
+
+                if (nodeDef.getRequiredPrimaryTypes().length > 1) {
+                    // behaviour of implementations that support multiple multiple inheritance
+                    // of primary node types is not specified
+                    continue;
+                }
+
+                if (regardDefaultPrimaryType) {
+
+                    if (defaultPrimaryType && nodeDef.getDefaultPrimaryType() == null) {
+                        continue;
+                    }
+
+                    if (!defaultPrimaryType && nodeDef.getDefaultPrimaryType() != null) {
+                        continue;
+                    }
+                }
+
+                if (residual && !nodeDef.getName().equals("*")) {
+                    continue;
+                }
+
+                if (!residual) {
+                    // if another child node def is a residual definition
+                    // skip the current node type
+                    NodeDefinition nodeDefsAll[] = type.getChildNodeDefinitions();
+                    for (int j = 0; j < nodeDefsAll.length; j++) {
+                        if (nodeDefsAll[j].getName().equals("*")) {
+                            skip = true;
+                            break;
+                        }
+                    }
+                    if (skip) {
+                        // break the loop of the current child not defs
+                        skip = false;
+                        break;
+                    }
+                }
+
+                nodeTypes.add(nodeDef);
+            }
+        }
+        return nodeTypes;
+    }
+
+    /**
      * Locate a child node def parsing all node types
      *
      * @param session     the session to access the node types
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/nodetype/PropertyDefTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/nodetype/PropertyDefTest.java
index 57532de..b0bc021 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/nodetype/PropertyDefTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/nodetype/PropertyDefTest.java
@@ -164,7 +164,7 @@ public class PropertyDefTest extends AbstractJCRTest {
     /**
      * This test checks if item definitions with mandatory constraints are
      * respected.
-     * <p/>
+     * <p>
      * If the default workspace does not contain a node with a node type
      * definition that specifies a mandatory property a {@link
      * org.apache.jackrabbit.test.NotExecutableException} is thrown.
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/AddEventListenerTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/AddEventListenerTest.java
index a258876..02427af 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/AddEventListenerTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/AddEventListenerTest.java
@@ -53,7 +53,7 @@ public class AddEventListenerTest extends AbstractObservationTest {
         obsMgr.addEventListener(listener, Event.NODE_ADDED, testRoot + "/" + nodeName1, true, null, null, false);
         Node n = testRootNode.addNode(nodeName1, testNodeType);
         n.addNode(nodeName2, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = listener.getEvents(DEFAULT_WAIT_TIMEOUT);
         obsMgr.removeEventListener(listener);
         checkNodeAdded(events, new String[]{nodeName1 + "/" + nodeName2}, null);
@@ -68,7 +68,7 @@ public class AddEventListenerTest extends AbstractObservationTest {
         obsMgr.addEventListener(listener, Event.NODE_ADDED, testRoot + "/" + nodeName1, false, null, null, false);
         Node n = testRootNode.addNode(nodeName1, testNodeType);
         n.addNode(nodeName2, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = listener.getEvents(DEFAULT_WAIT_TIMEOUT);
         obsMgr.removeEventListener(listener);
         checkNodeAdded(events, new String[]{nodeName1 + "/" + nodeName2}, null);
@@ -81,12 +81,12 @@ public class AddEventListenerTest extends AbstractObservationTest {
     public void testIsDeepFalsePropertyAdded() throws RepositoryException {
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         Node n2 = testRootNode.addNode(nodeName2, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult listener = new EventResult(log);
         obsMgr.addEventListener(listener, Event.PROPERTY_ADDED, testRoot + "/" + nodeName1, false, null, null, false);
         n1.setProperty(propertyName1, "foo");
         n2.setProperty(propertyName1, "foo");
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = listener.getEvents(DEFAULT_WAIT_TIMEOUT);
         obsMgr.removeEventListener(listener);
         checkPropertyAdded(events, new String[]{nodeName1 + "/" + propertyName1});
@@ -107,7 +107,7 @@ public class AddEventListenerTest extends AbstractObservationTest {
                 true); // noLocal
 
         testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = listener.getEvents(DEFAULT_WAIT_TIMEOUT);
         obsMgr.removeEventListener(listener);
         assertEquals("EventListener must not receive own modification when noLocal=true", 0, events.length);
@@ -125,7 +125,7 @@ public class AddEventListenerTest extends AbstractObservationTest {
         } catch (RepositoryException e) {
             throw new NotExecutableException("Repository does not support mix:referenceable");
         }
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult listener = new EventResult(log);
         obsMgr.addEventListener(listener,
                 Event.PROPERTY_ADDED,
@@ -136,7 +136,7 @@ public class AddEventListenerTest extends AbstractObservationTest {
                 false);
         n1.setProperty(propertyName1, "foo");
         n2.setProperty(propertyName1, "foo");
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = listener.getEvents(DEFAULT_WAIT_TIMEOUT);
         obsMgr.removeEventListener(listener);
         checkPropertyAdded(events, new String[]{nodeName1 + "/" + propertyName1});
@@ -150,7 +150,7 @@ public class AddEventListenerTest extends AbstractObservationTest {
         EventResult listener = new EventResult(log);
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         Node n2 = testRootNode.addNode(nodeName2, nodetype2);
-        testRootNode.save();
+        testRootNode.getSession().save();
         obsMgr.addEventListener(listener,
                 Event.NODE_ADDED,
                 testRoot,
@@ -191,7 +191,7 @@ public class AddEventListenerTest extends AbstractObservationTest {
             throws RepositoryException, NotExecutableException {
         Node n = testRootNode.addNode(nodeName, nodeType);
         ensureMixinType(n, mixReferenceable);
-        testRootNode.save();
+        testRootNode.getSession().save();
         return n;
     }
 }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/EventIteratorTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/EventIteratorTest.java
index 2e21018..a9bcc2d 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/EventIteratorTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/EventIteratorTest.java
@@ -53,7 +53,7 @@ public class EventIteratorTest extends AbstractObservationTest{
         EventResult listener = new EventResult(log);
         addEventListener(listener, Event.NODE_ADDED);
         testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventIterator events = listener.getEventIterator(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(listener);
         assertNotNull("No events delivered within " + DEFAULT_WAIT_TIMEOUT + "ms.", events);
@@ -73,7 +73,7 @@ public class EventIteratorTest extends AbstractObservationTest{
         testRootNode.addNode(nodeName1, testNodeType);
         testRootNode.addNode(nodeName2, testNodeType);
         testRootNode.addNode(nodeName3, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventIterator events = listener.getEventIterator(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(listener);
         assertNotNull("No events delivered within " + DEFAULT_WAIT_TIMEOUT + "ms.", events);
@@ -95,7 +95,7 @@ public class EventIteratorTest extends AbstractObservationTest{
         testRootNode.addNode(nodeName1, testNodeType);
         testRootNode.addNode(nodeName2, testNodeType);
         testRootNode.addNode(nodeName3, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventIterator events = listener.getEventIterator(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(listener);
         assertNotNull("No events delivered within " + DEFAULT_WAIT_TIMEOUT + "ms.", events);
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/EventJournalTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/EventJournalTest.java
index 8bac790..71fca13 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/EventJournalTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/EventJournalTest.java
@@ -21,6 +21,7 @@ import java.util.HashSet;
 import java.util.Set;
 
 import javax.jcr.Node;
+import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.observation.Event;
@@ -36,6 +37,7 @@ public class EventJournalTest extends AbstractObservationTest {
     private EventJournal journal;
 
     protected void setUp() throws Exception {
+        checkSupportedOption(Repository.OPTION_JOURNALED_OBSERVATION_SUPPORTED);
         super.setUp();
         journal = obsMgr.getEventJournal();
     }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/EventTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/EventTest.java
index 8f9ec4b..ccdb2ac 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/EventTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/EventTest.java
@@ -42,7 +42,7 @@ public class EventTest extends AbstractObservationTest {
         EventResult result = new EventResult(log);
         addEventListener(result, Event.NODE_ADDED);
         Node addedNode = testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(result);
         assertEquals("Wrong number of events returned", 1, events.length);
@@ -59,7 +59,7 @@ public class EventTest extends AbstractObservationTest {
         EventResult result = new EventResult(log);
         addEventListener(result, Event.NODE_ADDED);
         testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(result);
         assertEquals("Wrong number of events returned", 1, events.length);
@@ -75,7 +75,7 @@ public class EventTest extends AbstractObservationTest {
         EventResult result = new EventResult(log);
         addEventListener(result, Event.NODE_ADDED);
         testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(result);
         assertEquals("Wrong number of events returned", 1, events.length);
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/GetDateTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/GetDateTest.java
index 470e4b2..e0e6ee7 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/GetDateTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/GetDateTest.java
@@ -39,7 +39,7 @@ public class GetDateTest extends AbstractObservationTest {
             Event[] events = getEvents(new Callable() {
                 public void call() throws RepositoryException {
                     testRootNode.addNode(name, testNodeType);
-                    testRootNode.save();
+                    testRootNode.getSession().save();
                 }
             }, Event.NODE_ADDED);
             for (int i = 0; i < events.length; i++) {
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/GetIdentifierTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/GetIdentifierTest.java
index ab30d90..ec16221 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/GetIdentifierTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/GetIdentifierTest.java
@@ -30,7 +30,7 @@ public class GetIdentifierTest extends AbstractObservationTest {
         Event[] events = getEvents(new Callable(){
             public void call() throws RepositoryException {
                 testRootNode.addNode(nodeName1, testNodeType);
-                testRootNode.save();
+                testRootNode.getSession().save();
             }
         }, Event.NODE_ADDED);
         Node n = testRootNode.getNode(nodeName1);
@@ -39,7 +39,7 @@ public class GetIdentifierTest extends AbstractObservationTest {
 
     public void testNodeMoved() throws RepositoryException {
         final Node n = testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = getEvents(new Callable(){
             public void call() throws RepositoryException {
                 superuser.getWorkspace().move(n.getPath(), testRoot + "/" + nodeName2);
@@ -52,12 +52,12 @@ public class GetIdentifierTest extends AbstractObservationTest {
     public void testNodeRemoved() throws RepositoryException {
         final Node n = testRootNode.addNode(nodeName1, testNodeType);
         String path = n.getPath();
-        testRootNode.save();
+        testRootNode.getSession().save();
         String identifier = n.getIdentifier();
         Event[] events = getEvents(new Callable(){
             public void call() throws RepositoryException {
                 n.remove();
-                testRootNode.save();
+                testRootNode.getSession().save();
             }
         }, Event.NODE_REMOVED);
         assertEquals(identifier, getEventByPath(events, path).getIdentifier());
@@ -67,7 +67,7 @@ public class GetIdentifierTest extends AbstractObservationTest {
         Event[] events = getEvents(new Callable(){
             public void call() throws RepositoryException {
                 testRootNode.addNode(nodeName1, testNodeType).setProperty(propertyName1, "test");
-                testRootNode.save();
+                testRootNode.getSession().save();
             }
         }, Event.PROPERTY_ADDED);
         Node n = testRootNode.getNode(nodeName1);
@@ -78,11 +78,11 @@ public class GetIdentifierTest extends AbstractObservationTest {
     public void testPropertyChanged() throws RepositoryException {
         Node n = testRootNode.addNode(nodeName1, testNodeType);
         final Property prop = n.setProperty(propertyName1, "test");
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = getEvents(new Callable(){
             public void call() throws RepositoryException {
                 prop.setValue("modified");
-                testRootNode.save();
+                testRootNode.getSession().save();
             }
         }, Event.PROPERTY_CHANGED);
         assertEquals(n.getIdentifier(), getEventByPath(events, prop.getPath()).getIdentifier());
@@ -92,11 +92,11 @@ public class GetIdentifierTest extends AbstractObservationTest {
         Node n = testRootNode.addNode(nodeName1, testNodeType);
         final Property prop = n.setProperty(propertyName1, "test");
         String propPath = prop.getPath();
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = getEvents(new Callable(){
             public void call() throws RepositoryException {
                 prop.remove();
-                testRootNode.save();
+                testRootNode.getSession().save();
             }
         }, Event.PROPERTY_REMOVED);
         assertEquals(n.getIdentifier(), getEventByPath(events, propPath).getIdentifier());
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/GetInfoTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/GetInfoTest.java
index 93ddb68..fb96bfd 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/GetInfoTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/GetInfoTest.java
@@ -16,9 +16,13 @@
  */
 package org.apache.jackrabbit.test.api.observation;
 
-import javax.jcr.RepositoryException;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
 import javax.jcr.Node;
 import javax.jcr.Property;
+import javax.jcr.RepositoryException;
 import javax.jcr.observation.Event;
 
 /**
@@ -33,25 +37,27 @@ public class GetInfoTest extends AbstractObservationTest {
         Event[] events = getEvents(new Callable(){
             public void call() throws RepositoryException {
                 testRootNode.addNode(nodeName1, testNodeType);
-                testRootNode.save();
+                testRootNode.getSession().save();
             }
         }, Event.NODE_ADDED);
         for (int i = 0; i < events.length; i++) {
-            assertEquals("info map must be empty", 0, events[i].getInfo().size());
+            Set<?> unexpectedKeys = getUnexpectedKeys(events[i].getInfo());
+            assertEquals("info map contains invalid keys: " + unexpectedKeys, 0, unexpectedKeys.size());
         }
     }
 
     public void testNodeRemoved() throws RepositoryException {
         final Node n = testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = getEvents(new Callable(){
             public void call() throws RepositoryException {
                 n.remove();
-                testRootNode.save();
+                testRootNode.getSession().save();
             }
         }, Event.NODE_REMOVED);
         for (int i = 0; i < events.length; i++) {
-            assertEquals("info map must be empty", 0, events[i].getInfo().size());
+            Set<?> unexpectedKeys = getUnexpectedKeys(events[i].getInfo());
+            assertEquals("info map must be empty", 0, unexpectedKeys.size());
         }
     }
 
@@ -59,39 +65,50 @@ public class GetInfoTest extends AbstractObservationTest {
         Event[] events = getEvents(new Callable(){
             public void call() throws RepositoryException {
                 testRootNode.addNode(nodeName1, testNodeType).setProperty(propertyName1, "test");
-                testRootNode.save();
+                testRootNode.getSession().save();
             }
         }, Event.PROPERTY_ADDED);
         for (int i = 0; i < events.length; i++) {
-            assertEquals("info map must be empty", 0, events[i].getInfo().size());
+            Set<?> unexpectedKeys = getUnexpectedKeys(events[i].getInfo());
+            assertEquals("info map must be empty", 0, unexpectedKeys.size());
         }
     }
 
     public void testPropertyChanged() throws RepositoryException {
         Node n = testRootNode.addNode(nodeName1, testNodeType);
         final Property prop = n.setProperty(propertyName1, "test");
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = getEvents(new Callable(){
             public void call() throws RepositoryException {
                 prop.setValue("modified");
             }
         }, Event.PROPERTY_CHANGED);
         for (int i = 0; i < events.length; i++) {
-            assertEquals("info map must be empty", 0, events[i].getInfo().size());
+            Set<?> unexpectedKeys = getUnexpectedKeys(events[i].getInfo());
+            assertEquals("info map must be empty", 0, unexpectedKeys.size());
         }
     }
 
     public void testPropertyRemoved() throws RepositoryException {
         Node n = testRootNode.addNode(nodeName1, testNodeType);
         final Property prop = n.setProperty(propertyName1, "test");
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = getEvents(new Callable(){
             public void call() throws RepositoryException {
                 prop.remove();
             }
         }, Event.PROPERTY_REMOVED);
         for (int i = 0; i < events.length; i++) {
-            assertEquals("info map must be empty", 0, events[i].getInfo().size());
+            Set<?> unexpectedKeys = getUnexpectedKeys(events[i].getInfo());
+            assertEquals("info map must be empty", 0, unexpectedKeys.size());
         }
     }
+
+    private static Set<?> getUnexpectedKeys(Map<?, ?> info) {
+        Set<Object> result = new HashSet<Object>();
+        result.addAll(info.keySet());
+        result.remove("jcr:primaryType");
+        result.remove("jcr:mixinTypes");
+        return result;
+    }
 }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/GetUserDataTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/GetUserDataTest.java
index cd000fa..1600f48 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/GetUserDataTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/GetUserDataTest.java
@@ -33,14 +33,14 @@ public class GetUserDataTest extends AbstractObservationTest {
         runWithUserData(new Callable() {
             public void call() throws RepositoryException {
                 testRootNode.addNode(nodeName1, testNodeType);
-                testRootNode.save();
+                testRootNode.getSession().save();
             }
         }, ALL_TYPES);
     }
 
     public void testWorkspaceOperation() throws RepositoryException {
         testRootNode.addNode(nodeName1);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         runWithUserData(new Callable() {
             public void call() throws RepositoryException {
@@ -57,7 +57,7 @@ public class GetUserDataTest extends AbstractObservationTest {
 
         final Node n1 = testRootNode.addNode(nodeName1);
         ensureMixinType(n1, mixVersionable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         runWithUserData(new Callable() {
             public void call() throws RepositoryException {
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/LockingTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/LockingTest.java
index cfca975..37724ba 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/LockingTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/LockingTest.java
@@ -49,7 +49,7 @@ public class LockingTest extends AbstractObservationTest {
     public void testAddLockToNode() throws RepositoryException,
             NotExecutableException {
         Node lockable = createLockable(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult result = new EventResult(log);
         addEventListener(result, Event.PROPERTY_ADDED);
 
@@ -82,7 +82,7 @@ public class LockingTest extends AbstractObservationTest {
     public void testRemoveLockFromNode() throws RepositoryException,
             NotExecutableException {
         Node lockable = createLockable(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         // lock the node
         lockable.lock(false, true);
 
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/NodeAddedTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/NodeAddedTest.java
index b14e709..1bca62f 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/NodeAddedTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/NodeAddedTest.java
@@ -45,7 +45,7 @@ public class NodeAddedTest extends AbstractObservationTest {
         EventResult result = new EventResult(log);
         addEventListener(result, Event.NODE_ADDED);
         testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(result);
         checkNodeAdded(events, new String[]{nodeName1}, null);
@@ -60,7 +60,7 @@ public class NodeAddedTest extends AbstractObservationTest {
         addEventListener(result, Event.NODE_ADDED);
         testRootNode.addNode(nodeName1, testNodeType);
         testRootNode.addNode(nodeName2, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(result);
         checkNodeAdded(events, new String[]{nodeName1, nodeName2}, null);
@@ -75,7 +75,7 @@ public class NodeAddedTest extends AbstractObservationTest {
         addEventListener(result, Event.NODE_ADDED);
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         n1.addNode(nodeName2, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(result);
         checkNodeAdded(events, new String[]{nodeName1, nodeName1 + "/" + nodeName2}, null);
@@ -90,7 +90,7 @@ public class NodeAddedTest extends AbstractObservationTest {
         testRootNode.addNode(nodeName1, testNodeType);
         Node n2 = testRootNode.addNode(nodeName2, testNodeType);
         n2.remove();
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(result);
         checkNodeAdded(events, new String[]{nodeName1}, null);
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/NodeMovedTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/NodeMovedTest.java
index 10808ab..d089ef4 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/NodeMovedTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/NodeMovedTest.java
@@ -70,7 +70,7 @@ public class NodeMovedTest extends AbstractObservationTest {
 
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         n1.addNode(nodeName2, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult addNodeListener = new EventResult(log);
         EventResult removeNodeListener = new EventResult(log);
         EventResult moveNodeListener = new EventResult(log);
@@ -78,7 +78,7 @@ public class NodeMovedTest extends AbstractObservationTest {
         addEventListener(removeNodeListener, Event.NODE_REMOVED);
         addEventListener(moveNodeListener, Event.NODE_MOVED);
         superuser.move(n1.getPath(), testRoot + "/" + nodeName3);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] added = addNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
         Event[] removed = removeNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
         Event[] moved = moveNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
@@ -109,7 +109,7 @@ public class NodeMovedTest extends AbstractObservationTest {
 
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         Node n2 = n1.addNode(nodeName2, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult addNodeListener = new EventResult(log);
         EventResult removeNodeListener = new EventResult(log);
         EventResult moveNodeListener = new EventResult(log);
@@ -117,7 +117,7 @@ public class NodeMovedTest extends AbstractObservationTest {
         addEventListener(removeNodeListener, Event.NODE_REMOVED);
         addEventListener(moveNodeListener, Event.NODE_MOVED);
         superuser.move(n2.getPath(), testRoot + "/" + nodeName2);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] added = addNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
         Event[] removed = removeNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
         Event[] moved = moveNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
@@ -149,7 +149,7 @@ public class NodeMovedTest extends AbstractObservationTest {
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         Node n2 = n1.addNode(nodeName2, testNodeType);
         Node n3 = testRootNode.addNode(nodeName3, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult addNodeListener = new EventResult(log);
         EventResult removeNodeListener = new EventResult(log);
         EventResult moveNodeListener = new EventResult(log);
@@ -160,7 +160,7 @@ public class NodeMovedTest extends AbstractObservationTest {
         superuser.move(n2.getPath(), n3.getPath() + "/" + nodeName2);
         // remove n1
         n1.remove();
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] added = addNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
         Event[] removed = removeNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
         Event[] moved = moveNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/NodeRemovedTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/NodeRemovedTest.java
index e08c3a8..3788fb5 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/NodeRemovedTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/NodeRemovedTest.java
@@ -42,12 +42,12 @@ public class NodeRemovedTest extends AbstractObservationTest {
      * when a single node is removed.
      */
     public void testSingleNodeRemoved() throws RepositoryException {
+        Node foo = testRootNode.addNode(nodeName1, testNodeType);
+        testRootNode.getSession().save();
         EventResult result = new EventResult(log);
         addEventListener(result, Event.NODE_REMOVED);
-        Node foo = testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
         foo.remove();
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(result);
         checkNodeRemoved(events, new String[]{nodeName1}, null);
@@ -58,13 +58,13 @@ public class NodeRemovedTest extends AbstractObservationTest {
      * triggered when multiple nodes are removed.
      */
     public void testMultiNodesRemoved() throws RepositoryException {
-        EventResult result = new EventResult(log);
-        addEventListener(result, Event.NODE_REMOVED);
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         n1.addNode(nodeName2, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
+        EventResult result = new EventResult(log);
+        addEventListener(result, Event.NODE_REMOVED);
         n1.remove();
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(result);
         checkNodeRemoved(events, new String[]{nodeName1, nodeName1 + "/" + nodeName2}, null);
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/NodeReorderTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/NodeReorderTest.java
index cb4ede0..59a74bf 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/NodeReorderTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/NodeReorderTest.java
@@ -16,6 +16,9 @@
  */
 package org.apache.jackrabbit.test.api.observation;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.jackrabbit.test.NotExecutableException;
@@ -54,11 +57,7 @@ public class NodeReorderTest extends AbstractObservationTest {
      */
     private static final String DEST_CHILD_REL_PATH = "destChildRelPath";
 
-    /**
-     * Tests if reordering a child node triggers a {@link Event#NODE_REMOVED}
-     * and a {@link Event#NODE_ADDED} event.
-     */
-    public void testNodeReorder()
+    private void doTestNodeReorder(List<Event> added, List<Event> removed, List<Event> moved)
             throws RepositoryException, NotExecutableException {
         if (!testRootNode.getDefinition().getDeclaringNodeType().hasOrderableChildNodes()) {
             throw new NotExecutableException("Node at '" + testRoot + "' does not support orderable child nodes.");
@@ -80,7 +79,7 @@ public class NodeReorderTest extends AbstractObservationTest {
         testRootNode.addNode(nodeName1, testNodeType);
         testRootNode.addNode(nodeName2, testNodeType);
         testRootNode.addNode(nodeName3, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult addNodeListener = new EventResult(log);
         EventResult removeNodeListener = new EventResult(log);
         EventResult moveNodeListener = new EventResult(log);
@@ -88,13 +87,27 @@ public class NodeReorderTest extends AbstractObservationTest {
         addEventListener(removeNodeListener, Event.NODE_REMOVED);
         addEventListener(moveNodeListener, Event.NODE_MOVED);
         testRootNode.orderBefore(nodeName3, nodeName2);
-        testRootNode.save();
-        Event[] added = addNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
-        Event[] removed = removeNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
-        Event[] moved = moveNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
+        testRootNode.getSession().save();
+        added.addAll(Arrays.asList(addNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT)));
+        removed.addAll(Arrays.asList(removeNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT)));
+        moved.addAll(Arrays.asList(moveNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT)));
         removeEventListener(addNodeListener);
         removeEventListener(removeNodeListener);
         removeEventListener(moveNodeListener);
+    }
+
+    /**
+     * Tests if reordering a child node triggers a {@link Event#NODE_REMOVED}
+     * and a {@link Event#NODE_ADDED} event.
+     */
+    public void testNodeReorderAddRemove() throws RepositoryException, NotExecutableException {
+
+        List<Event> added = new ArrayList<Event>();
+        List<Event> removed = new ArrayList<Event>();
+        List<Event> moved = new ArrayList<Event>();
+
+        doTestNodeReorder(added, removed, moved);
+
         // either
         // 1) nodename2 has been reordered to the end
         // or:
@@ -106,24 +119,38 @@ public class NodeReorderTest extends AbstractObservationTest {
 
         // if true, check for option 1)
         boolean reorderEnd = false;
-        for (int i = 0; i < added.length; i++) {
-            if (added[i].getPath().endsWith(nodeName2)) {
+        for (Event e : added) {
+            if (e.getPath().endsWith(nodeName2)) {
                 reorderEnd = true;
                 break;
             }
         }
+
         if (reorderEnd) {
-            checkNodeAdded(added, new String[]{nodeName2}, null);
-            checkNodeRemoved(removed, new String[]{nodeName2}, null);
-            checkNodeReordered(moved, nodeName2, nodeName2, null);
+            checkNodeAdded(added, new String[] { nodeName2 }, null);
+            checkNodeRemoved(removed, new String[] { nodeName2 }, null);
         } else {
-            checkNodeAdded(added, new String[]{nodeName3}, null);
-            checkNodeRemoved(removed, new String[]{nodeName3}, null);
-            checkNodeReordered(moved, nodeName3, nodeName3, nodeName2);
+            checkNodeAdded(added, new String[] { nodeName3 }, null);
+            checkNodeRemoved(removed, new String[] { nodeName3 }, null);
         }
     }
 
     /**
+     * Tests if reordering a child node triggers a {@link Event#NODE_MOVED}
+     * event.
+     */
+    public void testNodeReorderMove() throws RepositoryException, NotExecutableException {
+
+        List<Event> added = new ArrayList<Event>();
+        List<Event> removed = new ArrayList<Event>();
+        List<Event> moved = new ArrayList<Event>();
+
+        doTestNodeReorder(added, removed, moved);
+
+        checkNodeReordered(moved, nodeName3, nodeName3, nodeName2);
+    }
+
+    /**
      * Tests if reordering a child node triggers a {@link Event#NODE_REMOVED}
      * and a {@link Event#NODE_ADDED} event with same name siblings.
      */
@@ -152,7 +179,7 @@ public class NodeReorderTest extends AbstractObservationTest {
         }
         testRootNode.addNode(nodeName1, testNodeType);
         testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult addNodeListener = new EventResult(log);
         EventResult removeNodeListener = new EventResult(log);
         EventResult moveNodeListener = new EventResult(log);
@@ -161,7 +188,7 @@ public class NodeReorderTest extends AbstractObservationTest {
         addEventListener(moveNodeListener, Event.NODE_MOVED);
         testRootNode.orderBefore(nodeName1 + "[3]", nodeName1 + "[2]");
         //testRootNode.orderBefore(nodeName1 + "[2]", null);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] added = addNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
         Event[] removed = removeNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
         Event[] moved = moveNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
@@ -231,7 +258,7 @@ public class NodeReorderTest extends AbstractObservationTest {
         testRootNode.addNode(nodeName1, testNodeType);
         testRootNode.addNode(nodeName1, testNodeType);
         testRootNode.addNode(nodeName3, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult addNodeListener = new EventResult(log);
         EventResult removeNodeListener = new EventResult(log);
         EventResult moveNodeListener = new EventResult(log);
@@ -240,7 +267,7 @@ public class NodeReorderTest extends AbstractObservationTest {
         addEventListener(moveNodeListener, Event.NODE_MOVED);
         testRootNode.orderBefore(nodeName1 + "[2]", null);
         testRootNode.getNode(nodeName3).remove();
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] added = addNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
         Event[] removed = removeNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
         Event[] moved = moveNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
@@ -297,6 +324,15 @@ public class NodeReorderTest extends AbstractObservationTest {
         checkInfoEntry(info, DEST_CHILD_REL_PATH, before);
     }
 
+    protected void checkNodeReordered(List<Event> events, String src, String dest, String before)
+            throws RepositoryException {
+        checkNodes(events.toArray(new Event[0]), new String[] { dest }, null, Event.NODE_MOVED);
+        assertEquals("Wrong number of events", 1, events.size());
+        Map<?, ?> info = events.get(0).getInfo();
+        checkInfoEntry(info, SRC_CHILD_REL_PATH, src);
+        checkInfoEntry(info, DEST_CHILD_REL_PATH, before);
+    }
+
     /**
      * Checks if the info map contains the given <code>key</code> with the
      * <code>expected</code> value.
@@ -310,4 +346,15 @@ public class NodeReorderTest extends AbstractObservationTest {
         assertEquals("Wrong event info value for: " + key,
                 expected, (String) info.get(key));
     }
+
+    protected void checkNodeAdded(List<Event> events, String[] requiredRelPaths, String[] optionalRelPaths)
+            throws RepositoryException {
+        checkNodes(events.toArray(new Event[0]), requiredRelPaths, optionalRelPaths, Event.NODE_ADDED);
+    }
+
+    protected void checkNodeRemoved(List<Event> events, String[] requiredRelPaths, String[] optionalRelPaths)
+            throws RepositoryException {
+        checkNodes(events.toArray(new Event[0]), requiredRelPaths, optionalRelPaths, Event.NODE_REMOVED);
+    }
+
 }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/PropertyAddedTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/PropertyAddedTest.java
index 25f6bc9..6cb84ba 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/PropertyAddedTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/PropertyAddedTest.java
@@ -48,7 +48,7 @@ public class PropertyAddedTest extends AbstractObservationTest {
         EventResult result = new EventResult(log);
         addEventListener(result, Event.PROPERTY_ADDED);
         testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(result);
         List<String> paths = new ArrayList<String>();
@@ -66,11 +66,11 @@ public class PropertyAddedTest extends AbstractObservationTest {
      */
     public void testSinglePropertyAdded() throws RepositoryException {
         Node foo = testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult result = new EventResult(log);
         addEventListener(result, Event.PROPERTY_ADDED);
         foo.setProperty(propertyName1, "test content");
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(result);
         checkPropertyAdded(events, new String[]{nodeName1 + "/" + propertyName1});
@@ -82,12 +82,12 @@ public class PropertyAddedTest extends AbstractObservationTest {
      */
     public void testMultiPropertyAdded() throws RepositoryException {
         Node foo = testRootNode.addNode(nodeName1, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult result = new EventResult(log);
         addEventListener(result, Event.PROPERTY_ADDED);
         foo.setProperty(propertyName1, "foo");
         foo.setProperty(propertyName2, "bar");
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(result);
         checkPropertyAdded(events, new String[]{nodeName1 + "/" + propertyName1,
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/PropertyChangedTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/PropertyChangedTest.java
index 58c5011..e8f5b1a 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/PropertyChangedTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/PropertyChangedTest.java
@@ -50,11 +50,11 @@ public class PropertyChangedTest extends AbstractObservationTest {
     public void testSinglePropertyChanged() throws RepositoryException {
         Node node = testRootNode.addNode(nodeName1, testNodeType);
         node.setProperty(propertyName1, "foo");
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult result = new EventResult(log);
         addEventListener(result, Event.PROPERTY_CHANGED);
         node.getProperty(propertyName1).setValue("foobar");
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(result);
         checkPropertyChanged(events, new String[]{nodeName1 + "/" + propertyName1});
@@ -69,12 +69,12 @@ public class PropertyChangedTest extends AbstractObservationTest {
         Node node = testRootNode.addNode(nodeName1, testNodeType);
         node.setProperty(propertyName1, "foo");
         node.setProperty(propertyName2, "bar");
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult result = new EventResult(log);
         addEventListener(result, Event.PROPERTY_CHANGED);
         node.getProperty(propertyName1).setValue("foobar");
         node.getProperty(propertyName2).setValue("foobar");
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(result);
         checkPropertyChanged(events, new String[]{nodeName1 + "/" + propertyName1,
@@ -88,12 +88,12 @@ public class PropertyChangedTest extends AbstractObservationTest {
     public void testSinglePropertyChangedWithAdded() throws RepositoryException {
         Node node = testRootNode.addNode(nodeName1, testNodeType);
         node.setProperty(propertyName1, "foo");
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult result = new EventResult(log);
         addEventListener(result, Event.PROPERTY_CHANGED);
         node.getProperty(propertyName1).setValue("foobar");
         node.setProperty(propertyName2, "bar");    // will not fire prop changed event
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(result);
         checkPropertyChanged(events, new String[]{nodeName1 + "/" + propertyName1});
@@ -122,12 +122,12 @@ public class PropertyChangedTest extends AbstractObservationTest {
             throw new NotExecutableException("Property " + propertyName1 + " is not of type UNDEFINED");
         }
         n.setProperty(propertyName1, v1);
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult result = new EventResult(log);
         addEventListener(result, Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED);
         n.getProperty(propertyName1).remove();
         n.setProperty(propertyName1, v2);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(result);
 
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/PropertyRemovedTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/PropertyRemovedTest.java
index 377c991..94d0eab 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/PropertyRemovedTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/PropertyRemovedTest.java
@@ -47,11 +47,11 @@ public class PropertyRemovedTest extends AbstractObservationTest {
         Node node = testRootNode.addNode(nodeName1, testNodeType);
         Property prop1 = node.setProperty(propertyName1, "foo");
         node.setProperty(propertyName2, "bar");
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult result = new EventResult(log);
         addEventListener(result, Event.PROPERTY_REMOVED);
         prop1.remove();
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(result);
         checkPropertyRemoved(events, new String[]{nodeName1 + "/" + propertyName1});
@@ -65,12 +65,12 @@ public class PropertyRemovedTest extends AbstractObservationTest {
         Node node = testRootNode.addNode(nodeName1, testNodeType);
         Property prop1 = node.setProperty(propertyName1, "foo");
         Property prop2 = node.setProperty(propertyName2, "bar");
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult result = new EventResult(log);
         addEventListener(result, Event.PROPERTY_REMOVED);
         prop1.remove();
         prop2.remove();
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] events = result.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(result);
         checkPropertyRemoved(events, new String[]{nodeName1 + "/" + propertyName1,
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/WorkspaceOperationTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/WorkspaceOperationTest.java
index 784baaa..1afaf44 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/WorkspaceOperationTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/observation/WorkspaceOperationTest.java
@@ -45,7 +45,7 @@ public class WorkspaceOperationTest extends AbstractObservationTest {
     public void testCopy() throws RepositoryException {
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         n1.addNode(nodeName2, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult listener = new EventResult(log);
         addEventListener(listener, Event.NODE_ADDED);
         superuser.getWorkspace().copy(testRoot + "/" + nodeName1, testRoot + "/" + nodeName3);
@@ -61,13 +61,13 @@ public class WorkspaceOperationTest extends AbstractObservationTest {
     public void testRename() throws RepositoryException {
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         n1.addNode(nodeName2, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult addNodeListener = new EventResult(log);
         EventResult removeNodeListener = new EventResult(log);
         addEventListener(addNodeListener, Event.NODE_ADDED);
         addEventListener(removeNodeListener, Event.NODE_REMOVED);
         superuser.getWorkspace().move(n1.getPath(), testRoot + "/" + nodeName3);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] added = addNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
         Event[] removed = removeNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(addNodeListener);
@@ -84,13 +84,13 @@ public class WorkspaceOperationTest extends AbstractObservationTest {
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         Node n3 = testRootNode.addNode(nodeName3, testNodeType);
         n1.addNode(nodeName2, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
         EventResult addNodeListener = new EventResult(log);
         EventResult removeNodeListener = new EventResult(log);
         addEventListener(addNodeListener, Event.NODE_ADDED);
         addEventListener(removeNodeListener, Event.NODE_REMOVED);
         superuser.getWorkspace().move(n1.getPath(), n3.getPath() + "/" + nodeName4);
-        testRootNode.save();
+        testRootNode.getSession().save();
         Event[] added = addNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
         Event[] removed = removeNodeListener.getEvents(DEFAULT_WAIT_TIMEOUT);
         removeEventListener(addNodeListener);
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/AbstractOrderByTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/AbstractOrderByTest.java
index f63d12c..8100711 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/AbstractOrderByTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/AbstractOrderByTest.java
@@ -149,13 +149,13 @@ class AbstractOrderByTest extends AbstractQueryTest {
         Query q;
         QueryResult result;
         if (sql != null) {
-            q = superuser.getWorkspace().getQueryManager().createQuery(sql, Query.SQL);
+            q = superuser.getWorkspace().getQueryManager().createQuery(sql, qsSQL);
             result = q.execute();
             checkResultOrder(result, nodeNames);
         }
 
         if (xpath != null) {
-            q = superuser.getWorkspace().getQueryManager().createQuery(xpath, Query.XPATH);
+            q = superuser.getWorkspace().getQueryManager().createQuery(xpath, qsXPATH);
             result = q.execute();
             checkResultOrder(result, nodeNames);
         }
@@ -168,13 +168,13 @@ class AbstractOrderByTest extends AbstractQueryTest {
         Collections.reverse(Arrays.asList(nodeNames));
 
         if (sql != null) {
-            q = superuser.getWorkspace().getQueryManager().createQuery(sql + " DESC", Query.SQL);
+            q = superuser.getWorkspace().getQueryManager().createQuery(sql + " DESC", qsSQL);
             result = q.execute();
             checkResultOrder(result, nodeNames);
         }
 
         if (xpath != null) {
-            q = superuser.getWorkspace().getQueryManager().createQuery(xpath + " descending", Query.XPATH);
+            q = superuser.getWorkspace().getQueryManager().createQuery(xpath + " descending", qsXPATH);
             result = q.execute();
             checkResultOrder(result, nodeNames);
         }
@@ -257,7 +257,7 @@ class AbstractOrderByTest extends AbstractQueryTest {
      */
     protected String createXPath() throws RepositoryException {
         List<String> languages = Arrays.asList(superuser.getWorkspace().getQueryManager().getSupportedQueryLanguages());
-        if (languages.contains(Query.XPATH)) {
+        if (languages.contains(qsXPATH)) {
             return xpathRoot + "/*[@jcr:primaryType='" + testNodeType + "'] order by @" + propertyName1;
         } else {
             return null;
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/AbstractQueryLevel2Test.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/AbstractQueryLevel2Test.java
index 9591d95..1b05c1f 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/AbstractQueryLevel2Test.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/AbstractQueryLevel2Test.java
@@ -42,7 +42,7 @@ public abstract class AbstractQueryLevel2Test extends AbstractQueryTest {
 
         node = testRootNode.addNode(nodeName2, testNodeType);
         node.setProperty(propertyName1, "The quick brown cat jumps over the lazy dog.");
-        testRootNode.save();
+        testRootNode.getSession().save();
     }
 
     /**
@@ -60,7 +60,7 @@ public abstract class AbstractQueryLevel2Test extends AbstractQueryTest {
 
         Node cNode = node.addNode(nodeName3, testNodeType);
         cNode.setProperty(propertyName1, "c");
-        testRootNode.save();
+        testRootNode.getSession().save();
     }
 
     /**
@@ -91,7 +91,7 @@ public abstract class AbstractQueryLevel2Test extends AbstractQueryTest {
 
         Node cNode = node.addNode(nodeName3, testNodeType);
         cNode.setProperty(propertyName1, "existence");
-        testRootNode.save();
+        testRootNode.getSession().save();
     }
 
     /**
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/AbstractQueryTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/AbstractQueryTest.java
index 4ccc2c1..29c54bc 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/AbstractQueryTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/AbstractQueryTest.java
@@ -93,6 +93,18 @@ public abstract class AbstractQueryTest extends AbstractJCRTest {
     protected QueryManager qm;
 
     /**
+     * The identifier for the "XPATH" query syntax
+     */
+    @SuppressWarnings("deprecation")
+	protected String qsXPATH = Query.XPATH;
+
+    /**
+     * The identifier for the "SQL" query syntax
+     */
+    @SuppressWarnings("deprecation")
+	protected String qsSQL = Query.SQL;
+
+    /**
      * Set-up the configuration values used for the test. Per default retrieves
      * a session, configures testRoot, and nodetype and checks if the query
      * language for the current language is available.<br>
@@ -147,14 +159,12 @@ public abstract class AbstractQueryTest extends AbstractJCRTest {
      */
     protected Query createQuery(Session session, String statement, String language) throws RepositoryException, NotExecutableException {
         log.println("Creating query: " + statement);
-        
+
         // check for unsupported query languages early
         if (! isSupportedLanguage(language) && !Query.JCR_SQL2.equals(language)) {
             throw new NotExecutableException("Repository does not support " + language + " query syntax");
         }
-        else {
-            return session.getWorkspace().getQueryManager().createQuery(statement, language);
-        }
+        return session.getWorkspace().getQueryManager().createQuery(statement, language);
     }
 
     /**
@@ -296,13 +306,13 @@ public abstract class AbstractQueryTest extends AbstractJCRTest {
      * the specified <code>nodes</code>.
      * @param session the session to use for the query.
      * @param xpath the xpath query.
-     * @param nodes the expected result nodes.
-     * @throws NotExecutableException 
+     * @param expectedNodes the expected nodes.
+     * @throws NotExecutableException
      */
-    protected void executeXPathQuery(Session session, String xpath, Node[] nodes)
+    protected void executeXPathQuery(Session session, String xpath, Node[] expectedNodes)
             throws RepositoryException, NotExecutableException {
-        QueryResult res = createQuery(session, xpath, Query.XPATH).execute();
-        checkResult(res, nodes);
+        QueryResult res = createQuery(session, xpath, qsXPATH).execute();
+        checkResult(res, expectedNodes, null);
     }
 
     /**
@@ -310,43 +320,82 @@ public abstract class AbstractQueryTest extends AbstractJCRTest {
      * the specified <code>nodes</code>.
      * @param session the session to use for the query.
      * @param sql the sql query.
-     * @param nodes the expected result nodes.
-     * @throws NotExecutableException 
+     * @param expectedNodes the expected nodes.
+     * @throws NotExecutableException
      */
-    protected void executeSqlQuery(Session session, String sql, Node[] nodes)
+    protected void executeSqlQuery(Session session, String sql, Node[] expectedNodes)
             throws RepositoryException, NotExecutableException {
-        QueryResult res = createQuery(session, sql, Query.SQL).execute();
-        checkResult(res, nodes);
+    		executeSqlQuery(session, sql, expectedNodes, null);
+    }
+
+    /**
+     * Executes the <code>sql</code> query and checks the results against
+     * the specified <code>nodes</code>.
+     * @param session the session to use for the query.
+     * @param sql the sql query.
+     * @param requiredNodes the nodes that need to be in the result set
+     * 		(null if no node is required).
+     * @param optionalNodes the nodes that may be in the result set
+     * 		(null if no node is optional).
+     * @throws NotExecutableException
+     */
+    protected void executeSqlQuery(Session session, String sql, Node[] requiredNodes, Node[] optionalNodes)
+            throws RepositoryException, NotExecutableException {
+        QueryResult res = createQuery(session, sql, qsSQL).execute();
+        checkResult(res, requiredNodes, optionalNodes);
     }
 
     /**
      * Checks if the result set contains exactly the <code>nodes</code>.
      * @param result the query result.
-     * @param nodes the expected nodes in the result set.
+     * @param expectedNodes the expected nodes.
      */
-    protected void checkResult(QueryResult result, Node[] nodes)
+    protected void checkResult(QueryResult result, Node[] expectedNodes)
+    			throws RepositoryException {
+    		checkResult(result, expectedNodes, null);
+	}
+
+    /**
+     * Checks if the result set contains exactly the <code>nodes</code>.
+     * @param result the query result.
+     * @param requiredNodes the nodes that need to be in the result set
+     * 		(null if no node is required).
+     * @param optionalNodes the nodes that may be in the result set
+     * 		(null if no node is optional).
+     */
+    protected void checkResult(QueryResult result, Node[] requiredNodes, Node[] optionalNodes)
             throws RepositoryException {
         // collect paths
-        Set<String> expectedPaths = new HashSet<String>();
-        for (int i = 0; i < nodes.length; i++) {
-            expectedPaths.add(nodes[i].getPath());
-        }
+        Set<String> requiredPaths = getPathSet(requiredNodes);
+        Set<String> optionalPaths = getPathSet(optionalNodes);
         Set<String> resultPaths = new HashSet<String>();
         for (NodeIterator it = result.getNodes(); it.hasNext();) {
             resultPaths.add(it.nextNode().getPath());
         }
-        // check if all expected are in result
-        for (Iterator<String> it = expectedPaths.iterator(); it.hasNext();) {
+        // check if all required nodes are in result
+        for (Iterator<String> it = requiredPaths.iterator(); it.hasNext();) {
             String path = it.next();
             assertTrue(path + " is not part of the result set", resultPaths.contains(path));
         }
         // check result does not contain more than expected
         for (Iterator<String> it = resultPaths.iterator(); it.hasNext();) {
             String path = it.next();
-            assertTrue(path + " is not expected to be part of the result set", expectedPaths.contains(path));
+            if (!optionalPaths.contains(path)) {
+            		assertTrue(path + " is not expected to be part of the result set", requiredPaths.contains(path));
+            }
         }
     }
 
+    private static HashSet<String> getPathSet(Node[] nodes) throws RepositoryException {
+    		HashSet<String> paths = new HashSet<String>();
+    		if (nodes != null) {
+    	        for (int i = 0; i < nodes.length; i++) {
+    	        		paths.add(nodes[i].getPath());
+        		}
+    		}
+    		return paths;
+    }
+
     /**
      * Returns the nodes in <code>it</code> as an array of Nodes.
      * @param it the NodeIterator.
@@ -371,9 +420,7 @@ public abstract class AbstractQueryTest extends AbstractJCRTest {
         if (!needsEscaping) {
             return identifier;
         }
-        else {
-            return '"' + identifier + '"';
-        }
+        return '"' + identifier + '"';
     }
 
     /**
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/ElementTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/ElementTest.java
index 575e98a..c38d738 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/ElementTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/ElementTest.java
@@ -57,7 +57,7 @@ public class ElementTest extends AbstractQueryTest {
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         Node n2 = testRootNode.addNode(nodeName2, simpleNodeType);
         Node n3 = testRootNode.addNode(nodeName3, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         String query = "/" + jcrRoot + testRoot + "/element()";
         executeXPathQuery(superuser, query, new Node[]{n1, n2, n3});
@@ -71,7 +71,7 @@ public class ElementTest extends AbstractQueryTest {
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         Node n2 = testRootNode.addNode(nodeName2, simpleNodeType);
         Node n3 = testRootNode.addNode(nodeName3, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         String query = "/" + jcrRoot + testRoot + "/element(*)";
         executeXPathQuery(superuser, query, new Node[]{n1, n2, n3});
@@ -86,7 +86,7 @@ public class ElementTest extends AbstractQueryTest {
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         Node n2 = testRootNode.addNode(nodeName2, simpleNodeType);
         Node n3 = testRootNode.addNode(nodeName3, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         String query = "/" + jcrRoot + testRoot + "/element(*, " + ntBase + ")";
         executeXPathQuery(superuser, query, new Node[]{n1, n2, n3});
@@ -101,7 +101,7 @@ public class ElementTest extends AbstractQueryTest {
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         testRootNode.addNode(nodeName2, simpleNodeType);
         Node n3 = testRootNode.addNode(nodeName3, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         String query = "/" + jcrRoot + testRoot + "/element(*, " + testNodeType + ")";
         executeXPathQuery(superuser, query, new Node[]{n1, n3});
@@ -115,7 +115,7 @@ public class ElementTest extends AbstractQueryTest {
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         testRootNode.addNode(nodeName2, simpleNodeType);
         testRootNode.addNode(nodeName3, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         String query = "/" + jcrRoot + testRoot + "/element(" + nodeName1 + ")";
         executeXPathQuery(superuser, query, new Node[]{n1});
@@ -130,7 +130,7 @@ public class ElementTest extends AbstractQueryTest {
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         testRootNode.addNode(nodeName2, simpleNodeType);
         testRootNode.addNode(nodeName3, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         String query = "/" + jcrRoot + testRoot + "/element(" + nodeName1 + ", " + ntBase + ")";
         executeXPathQuery(superuser, query, new Node[]{n1});
@@ -145,7 +145,7 @@ public class ElementTest extends AbstractQueryTest {
         Node n1 = testRootNode.addNode(nodeName1, testNodeType);
         testRootNode.addNode(nodeName2, simpleNodeType);
         testRootNode.addNode(nodeName3, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         String query = "/" + jcrRoot + testRoot + "/element(" + nodeName1 + ", " + testNodeType + ")";
         executeXPathQuery(superuser, query, new Node[]{n1});
@@ -165,7 +165,7 @@ public class ElementTest extends AbstractQueryTest {
         Node n2 = testRootNode.addNode(nodeName1, testNodeType);
         testRootNode.addNode(nodeName2, simpleNodeType);
         testRootNode.addNode(nodeName3, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         String query = "/" + jcrRoot + testRoot + "/element(" + nodeName1 + ", " + testNodeType + ")";
         executeXPathQuery(superuser, query, new Node[]{n1, n2});
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetLanguageTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetLanguageTest.java
index c9c36a9..f06dede 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetLanguageTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetLanguageTest.java
@@ -63,8 +63,8 @@ public class GetLanguageTest extends AbstractQueryTest {
      */
     public void testGetLanguage() throws RepositoryException {
         String statement = "/" + jcrRoot;
-        Query q = session.getWorkspace().getQueryManager().createQuery(statement, Query.XPATH);
-        assertEquals("Query returns wrong language.", Query.XPATH, q.getLanguage());
+        Query q = session.getWorkspace().getQueryManager().createQuery(statement, qsXPATH);
+        assertEquals("Query returns wrong language.", qsXPATH, q.getLanguage());
     }
 
     /**
@@ -72,10 +72,10 @@ public class GetLanguageTest extends AbstractQueryTest {
      * {@link Query#getLanguage()}.
      */
     public void testSQL() throws RepositoryException, NotExecutableException {
-        if (isSupportedLanguage(Query.SQL)) {
+        if (isSupportedLanguage(qsSQL)) {
             String stmt = "select * from " + testNodeType;
-            Query q = session.getWorkspace().getQueryManager().createQuery(stmt, Query.SQL);
-            assertEquals("Query returns wrong language.", Query.SQL, q.getLanguage());
+            Query q = session.getWorkspace().getQueryManager().createQuery(stmt, qsSQL);
+            assertEquals("Query returns wrong language.", qsSQL, q.getLanguage());
         } else {
             throw new NotExecutableException("SQL not supported");
         }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetPersistentQueryPathLevel1Test.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetPersistentQueryPathLevel1Test.java
index 81331ae..3bf332b 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetPersistentQueryPathLevel1Test.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetPersistentQueryPathLevel1Test.java
@@ -61,7 +61,7 @@ public class GetPersistentQueryPathLevel1Test extends AbstractQueryTest {
      */
     public void testGetStoredQueryPath() throws RepositoryException {
         String statement = "/" + jcrRoot;
-        Query q = session.getWorkspace().getQueryManager().createQuery(statement, Query.XPATH);
+        Query q = session.getWorkspace().getQueryManager().createQuery(statement, qsXPATH);
         try {
             q.getStoredQueryPath();
             fail("Query.getStoredQueryPath() on a transient query must throw an ItemNotFoundException.");
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetPersistentQueryPathTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetPersistentQueryPathTest.java
index 3226bb1..2994bfc 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetPersistentQueryPathTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetPersistentQueryPathTest.java
@@ -51,7 +51,7 @@ public class GetPersistentQueryPathTest extends AbstractQueryTest {
             throw new NotExecutableException("repository does not support nt:query");
         }
         String statement = "/" + jcrRoot;
-        Query q = superuser.getWorkspace().getQueryManager().createQuery(statement, Query.XPATH);
+        Query q = superuser.getWorkspace().getQueryManager().createQuery(statement, qsXPATH);
         String path = testRoot + "/" + nodeName1;
         q.storeAsNode(path);
         assertEquals("Query.getPersistentQueryPath() does not return the correct path.",
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetPropertyNamesTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetPropertyNamesTest.java
index 29c7377..6cdb563 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetPropertyNamesTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetPropertyNamesTest.java
@@ -68,7 +68,7 @@ public class GetPropertyNamesTest extends AbstractQueryTest {
         String queryStatement = "/" + jcrRoot;
 
         // build and execute search query
-        Query query = superuser.getWorkspace().getQueryManager().createQuery(queryStatement, Query.XPATH);
+        Query query = superuser.getWorkspace().getQueryManager().createQuery(queryStatement, qsXPATH);
         QueryResult result = query.execute();
 
         // Get the node's non-residual properties
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetStatementTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetStatementTest.java
index 0131adb..73fe090 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetStatementTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetStatementTest.java
@@ -60,7 +60,7 @@ public class GetStatementTest extends AbstractQueryTest {
      */
     public void testGetStatement() throws RepositoryException {
         String statement = "/" + jcrRoot + "/foo";
-        Query q = session.getWorkspace().getQueryManager().createQuery(statement, Query.XPATH);
+        Query q = session.getWorkspace().getQueryManager().createQuery(statement, qsXPATH);
         assertEquals("Statement returned by Query.getStatement() is not equal to the initial statement.",
                 statement,
                 q.getStatement());
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetSupportedQueryLanguagesTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetSupportedQueryLanguagesTest.java
index 2c5afe7..baa9aa7 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetSupportedQueryLanguagesTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/GetSupportedQueryLanguagesTest.java
@@ -72,7 +72,7 @@ public class GetSupportedQueryLanguagesTest extends AbstractQueryTest {
         // if repository descriptor for sql is present also sql must be returned
         if (isSupported(Repository.OPTION_QUERY_SQL_SUPPORTED)) {
             assertTrue("SQL not returned with QueryManager.getSupportedQueryLanguages()",
-                    langs.contains(Query.SQL));
+                    langs.contains(qsSQL));
         }
 
     }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/OrderByMultiTypeTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/OrderByMultiTypeTest.java
index 17e40e7..9f4c2fc 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/OrderByMultiTypeTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/OrderByMultiTypeTest.java
@@ -54,7 +54,7 @@ public class OrderByMultiTypeTest extends AbstractOrderByTest {
         n3.setProperty(propertyName1, "ccc");
         n3.setProperty(propertyName2, 2);
 
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // both ascending
         String sql = "SELECT " + propertyName2 + " FROM " + testNodeType + " WHERE " +
@@ -62,14 +62,14 @@ public class OrderByMultiTypeTest extends AbstractOrderByTest {
         Query q;
         QueryResult result;
         if (checkSQL) {
-            q = superuser.getWorkspace().getQueryManager().createQuery(sql, Query.SQL);
+            q = superuser.getWorkspace().getQueryManager().createQuery(sql, qsSQL);
             result = q.execute();
             checkResultOrder(result, new String[]{nodeName2, nodeName3, nodeName1});
         }
 
-        String xpath = "/" + testRoot + "/*[@" + jcrPrimaryType + "='" + testNodeType +
+        String xpath = "/" + jcrRoot + testRoot + "/*[@" + jcrPrimaryType + "='" + testNodeType +
                 "'] order by @" + propertyName2 + ", @" + propertyName1;
-        q = superuser.getWorkspace().getQueryManager().createQuery(xpath, Query.XPATH);
+        q = superuser.getWorkspace().getQueryManager().createQuery(xpath, qsXPATH);
         result = q.execute();
         checkResultOrder(result, new String[]{nodeName2, nodeName3, nodeName1});
 
@@ -79,7 +79,7 @@ public class OrderByMultiTypeTest extends AbstractOrderByTest {
                 propertyName2 + " DESC, " +
                 propertyName1 + " DESC";
         if (checkSQL) {
-            q = superuser.getWorkspace().getQueryManager().createQuery(sql, Query.SQL);
+            q = superuser.getWorkspace().getQueryManager().createQuery(sql, qsSQL);
             result = q.execute();
             checkResultOrder(result, new String[]{nodeName1, nodeName3, nodeName2});
         }
@@ -88,7 +88,7 @@ public class OrderByMultiTypeTest extends AbstractOrderByTest {
                 testNodeType + "'] order by @" +
                 propertyName2 + " descending, @" +
                 propertyName1 + " descending";
-        q = superuser.getWorkspace().getQueryManager().createQuery(xpath, Query.XPATH);
+        q = superuser.getWorkspace().getQueryManager().createQuery(xpath, qsXPATH);
         result = q.execute();
         checkResultOrder(result, new String[]{nodeName1, nodeName3, nodeName2});
 
@@ -97,7 +97,7 @@ public class OrderByMultiTypeTest extends AbstractOrderByTest {
                 jcrPath + " LIKE '" + testRoot + "/%' ORDER BY " +
                 propertyName2 + " DESC, " + propertyName1;
         if (checkSQL) {
-            q = superuser.getWorkspace().getQueryManager().createQuery(sql, Query.SQL);
+            q = superuser.getWorkspace().getQueryManager().createQuery(sql, qsSQL);
             result = q.execute();
             checkResultOrder(result, new String[]{nodeName1, nodeName2, nodeName3});
         }
@@ -105,7 +105,7 @@ public class OrderByMultiTypeTest extends AbstractOrderByTest {
         xpath = "/" + jcrRoot + testRoot + "/*[@" + jcrPrimaryType + "='" +
                 testNodeType + "'] order by @" + propertyName2 +
                 " descending, @" + propertyName1;
-        q = superuser.getWorkspace().getQueryManager().createQuery(xpath, Query.XPATH);
+        q = superuser.getWorkspace().getQueryManager().createQuery(xpath, qsXPATH);
         result = q.execute();
         checkResultOrder(result, new String[]{nodeName1, nodeName2, nodeName3});
     }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/PredicatesTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/PredicatesTest.java
index 2766136..4a1b1e6 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/PredicatesTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/PredicatesTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.jackrabbit.test.api.query;
 
-import javax.jcr.query.Query;
 import javax.jcr.query.InvalidQueryException;
 import javax.jcr.query.QueryManager;
 import javax.jcr.RepositoryException;
@@ -84,7 +83,7 @@ public class PredicatesTest extends AbstractQueryTest {
             xpathRoot + "/*[@" + jcrPrimaryType + "='" + nodeTypeName + "']";
 
         try {
-            qm.createQuery(stmt, Query.XPATH);
+            qm.createQuery(stmt, qsXPATH);
         } catch (InvalidQueryException e) {
             fail("invalid statement syntax for '" + stmt + "'");
         }
@@ -101,7 +100,7 @@ public class PredicatesTest extends AbstractQueryTest {
             + "' or @" + jcrPrimaryType + "='" + ntBase + "']";
 
         try {
-            qm.createQuery(stmt, Query.XPATH);
+            qm.createQuery(stmt, qsXPATH);
         } catch (InvalidQueryException e) {
             fail("invalid statement syntax for '" + stmt + "'");
         }
@@ -117,7 +116,7 @@ public class PredicatesTest extends AbstractQueryTest {
             xpathRoot + "/*[@" + jcrPrimaryType + " or @" + jcrMixinTypes + "]";
 
         try {
-            qm.createQuery(stmt, Query.XPATH);
+            qm.createQuery(stmt, qsXPATH);
         } catch (InvalidQueryException e) {
             fail("invalid statement syntax for '" + stmt + "'");
         }
@@ -133,7 +132,7 @@ public class PredicatesTest extends AbstractQueryTest {
             xpathRoot + "/*[@" + jcrPrimaryType + " and @" + jcrMixinTypes + "]";
 
         try {
-            qm.createQuery(stmt, Query.XPATH);
+            qm.createQuery(stmt, qsXPATH);
         } catch (InvalidQueryException e) {
             fail("invalid statement syntax for '" + stmt + "'");
         }
@@ -150,7 +149,7 @@ public class PredicatesTest extends AbstractQueryTest {
             + "' and @" + jcrPrimaryType + "='" + ntBase + "']";
 
         try {
-            qm.createQuery(stmt, Query.XPATH);
+            qm.createQuery(stmt, qsXPATH);
         } catch (InvalidQueryException e) {
             fail("invalid statement syntax for '" + stmt + "'");
         }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/QueryResultNodeIteratorTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/QueryResultNodeIteratorTest.java
index f78e553..ec05c3e 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/QueryResultNodeIteratorTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/QueryResultNodeIteratorTest.java
@@ -20,7 +20,6 @@ import org.apache.jackrabbit.test.NotExecutableException;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.NodeIterator;
-import javax.jcr.query.Query;
 import javax.jcr.query.QueryResult;
 import java.util.NoSuchElementException;
 
@@ -43,7 +42,7 @@ public class QueryResultNodeIteratorTest extends AbstractQueryTest {
         testRootNode.addNode(nodeName1, testNodeType);
         testRootNode.addNode(nodeName2, testNodeType);
         testRootNode.addNode(nodeName3, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
     }
 
     /**
@@ -54,7 +53,7 @@ public class QueryResultNodeIteratorTest extends AbstractQueryTest {
      *          if getSize() returns -1 (unavailable).
      */
     public void testGetSize() throws RepositoryException, NotExecutableException {
-        NodeIterator it = execute(xpathRoot + "//*", Query.XPATH).getNodes();
+        NodeIterator it = execute(xpathRoot + "//*", qsXPATH).getNodes();
         long size = testRootNode.getNodes().getSize();
         if (size != -1) {
             long count = 0;
@@ -72,7 +71,7 @@ public class QueryResultNodeIteratorTest extends AbstractQueryTest {
      * Tests the method <code>NodeIterator.getPosition()</code>.
      */
     public void testGetPosition() throws RepositoryException, NotExecutableException {
-        QueryResult rs = execute(xpathRoot + "//*", Query.XPATH);
+        QueryResult rs = execute(xpathRoot + "//*", qsXPATH);
 
         // getPosition initially returns 0
         NodeIterator it = rs.getNodes();
@@ -92,7 +91,7 @@ public class QueryResultNodeIteratorTest extends AbstractQueryTest {
      * @throws NotExecutableException 
      */
     public void testGetPositionEmptyIterator() throws RepositoryException, NotExecutableException {
-        QueryResult rs = execute(xpathRoot + "/" + nodeName4, Query.XPATH);
+        QueryResult rs = execute(xpathRoot + "/" + nodeName4, qsXPATH);
 
         NodeIterator it = rs.getNodes();
         assertFalse("NodeIterator must be empty.", it.hasNext());
@@ -107,7 +106,7 @@ public class QueryResultNodeIteratorTest extends AbstractQueryTest {
      * @throws NotExecutableException 
      */
     public void testNoSuchElementException() throws RepositoryException, NotExecutableException {
-        NodeIterator it = execute(xpathRoot + "//*", Query.XPATH).getNodes();
+        NodeIterator it = execute(xpathRoot + "//*", qsXPATH).getNodes();
         while (it.hasNext()) {
             it.nextNode();
         }
@@ -125,7 +124,7 @@ public class QueryResultNodeIteratorTest extends AbstractQueryTest {
      */
     public void testSkip() throws RepositoryException, NotExecutableException {
         String query = xpathRoot + "//*";
-        QueryResult rs = execute(query, Query.XPATH);
+        QueryResult rs = execute(query, qsXPATH);
         NodeIterator it = rs.getNodes();
 
         // find out if there is anything we can skip
@@ -136,7 +135,7 @@ public class QueryResultNodeIteratorTest extends AbstractQueryTest {
         }
         if (count > 1) {
             // re-execute the query
-            rs = execute(query, Query.XPATH);
+            rs = execute(query, qsXPATH);
             it = rs.getNodes();
             // skip all but one
             it.skip(count - 1);
@@ -150,7 +149,7 @@ public class QueryResultNodeIteratorTest extends AbstractQueryTest {
             }
 
             // re-execute the query
-            rs = execute(query, Query.XPATH);
+            rs = execute(query, qsXPATH);
             it = rs.getNodes();
             try {
                 it.skip(count + 1);
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLJcrPathTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLJcrPathTest.java
index b595bf9..69dad7a 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLJcrPathTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLJcrPathTest.java
@@ -66,7 +66,7 @@ public class SQLJcrPathTest extends AbstractQueryTest {
         String queryStatement = "select * from " + nodeTypeName;
 
         // execute the search query
-        Query query = super.createQuery(queryStatement, Query.SQL);
+        Query query = super.createQuery(queryStatement, qsSQL);
         QueryResult result = query.execute();
 
         assertTrue("jcr:path must be present in query result row",
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLJoinTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLJoinTest.java
index ac1acef..0cc148e 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLJoinTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLJoinTest.java
@@ -54,7 +54,7 @@ public class SQLJoinTest extends AbstractQueryTest {
         }
 
         Node n2 = testRootNode.addNode(nodeName2, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         assertFalse("Node at " + n2.getPath() + " should not have mixin " + testMixin, n2.isNodeType(testMixin));
 
@@ -87,7 +87,7 @@ public class SQLJoinTest extends AbstractQueryTest {
         }
 
         Node n2 = testRootNode.addNode(nodeName2, testNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         assertFalse("Node at " + n2.getPath() + " should not have mixin " + testMixin, n2.isNodeType(testMixin));
 
@@ -113,7 +113,7 @@ public class SQLJoinTest extends AbstractQueryTest {
         String nodetype = testNodeTypeNoChildren == null ? ntBase : testNodeTypeNoChildren;
         Node n2 = testRootNode.addNode(nodeName2, nodetype);
         ensureMixinType(n2, mixReferenceable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         StringBuffer query = new StringBuffer("SELECT * FROM ");
         query.append(testNodeType).append(", ").append(ntBase);
@@ -144,7 +144,7 @@ public class SQLJoinTest extends AbstractQueryTest {
         testRootNode.addNode(nodeName1, testNodeType);
         Node n2 = testRootNode.addNode(nodeName2, testNodeType);
         ensureMixinType(n2, mixReferenceable);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         StringBuffer query = new StringBuffer("SELECT * FROM ");
         query.append(testNodeType).append(", ").append(mixReferenceable);
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLOrderByTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLOrderByTest.java
index 21a62a6..89153ac 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLOrderByTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLOrderByTest.java
@@ -16,9 +16,6 @@
  */
 package org.apache.jackrabbit.test.api.query;
 
-import javax.jcr.query.Query;
-
-
 /**
  * This test searches for all nodes of a specific node type and orders them by
  * the property with name configured as {@link #propertyName1}.
@@ -61,7 +58,7 @@ public class SQLOrderByTest extends AbstractQueryTest {
      * For configuration description see {@link SQLOrderByTest}.
      */
     public void testOrderByAscending() throws Exception {
-        Statement stmt = new Statement(baseStatement + " ASC", Query.SQL);
+        Statement stmt = new Statement(baseStatement + " ASC", qsSQL);
         evaluateResultOrder(execute(stmt), propertyName1, false);
     }
 
@@ -71,7 +68,7 @@ public class SQLOrderByTest extends AbstractQueryTest {
      * For configuration description see {@link SQLOrderByTest}.
      */
     public void testOrderByDescending() throws Exception {
-        Statement stmt = new Statement(baseStatement + " DESC", Query.SQL);
+        Statement stmt = new Statement(baseStatement + " DESC", qsSQL);
         evaluateResultOrder(execute(stmt), propertyName1, true);
     }
 
@@ -82,7 +79,7 @@ public class SQLOrderByTest extends AbstractQueryTest {
      * For configuration description see {@link SQLOrderByTest}.
      */
     public void testOrderByDefault() throws Exception {
-        Statement stmt = new Statement(baseStatement, Query.SQL);
+        Statement stmt = new Statement(baseStatement, qsSQL);
         evaluateResultOrder(execute(stmt), propertyName1, false);
     }
 }
\ No newline at end of file
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLPathTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLPathTest.java
index da2c08a..e9b3fe9 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLPathTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLPathTest.java
@@ -69,7 +69,7 @@ public class SQLPathTest extends AbstractQueryTest {
 
     /**
      * Tests if <somepath>/% returns the descendants of <somepath>.
-     * @throws NotExecutableException 
+     * @throws NotExecutableException
      */
     public void testDescendantTestRoot() throws RepositoryException, NotExecutableException {
         String sql = getStatement(testRoot + "/%");
@@ -79,7 +79,7 @@ public class SQLPathTest extends AbstractQueryTest {
     /**
      * Tests if <somepath>/% returns no nodes if node at <somepath>
      * is a leaf.
-     * @throws NotExecutableException 
+     * @throws NotExecutableException
      */
     public void testDescendantLeaf() throws RepositoryException, NotExecutableException {
         // find leaf
@@ -95,7 +95,7 @@ public class SQLPathTest extends AbstractQueryTest {
      * Tests if <somepath>/%/<nodename> OR <somepath>/<nodename>
      * returns nodes with name <nodename> which are descendants of
      * node at <code>testroot</code>.
-     * @throws NotExecutableException 
+     * @throws NotExecutableException
      */
     public void testDescendantSelfTestRoot() throws RepositoryException, NotExecutableException {
         // get first node which is two levels deeper than node at testroot
@@ -124,19 +124,23 @@ public class SQLPathTest extends AbstractQueryTest {
 
     /**
      * Tests if /% AND NOT /%/% returns the child nodes of the root node.
-     * @throws NotExecutableException 
+     * @throws NotExecutableException
      */
     public void testChildAxisRoot() throws RepositoryException, NotExecutableException {
         String sql = getStatement("/%");
         sql += " AND NOT " + jcrPath + " LIKE '/%/%'";
         Node[] nodes = toArray(session.getRootNode().getNodes());
-        executeSqlQuery(session, sql, nodes);
+        // optionally, the result may include the root node -
+        // the specification allows to not return it even if using jcr:path LIKE '/%'
+        // see also the JCR 1.0 specification, section 8.5.2.2 ("Pseudo-property jcr:path")
+        Node[] optional = { session.getRootNode() };
+        executeSqlQuery(session, sql, nodes, optional);
     }
 
     /**
      * Tests if <somepath>/% AND NOT <somepath>/%/% returns the child
      * nodes of node at <somepath>.
-     * @throws NotExecutableException 
+     * @throws NotExecutableException
      */
     public void testChildAxisTestRoot() throws RepositoryException, NotExecutableException {
         String sql = getStatement(testRoot + "/%");
@@ -148,7 +152,7 @@ public class SQLPathTest extends AbstractQueryTest {
     /**
      * Tests if <somepath>/% AND NOT <somepath>/%/% returns no nodes
      * if the node at <somepath> is a leaf.
-     * @throws NotExecutableException 
+     * @throws NotExecutableException
      */
     public void testChildAxisLeaf() throws RepositoryException, NotExecutableException {
         // find leaf
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLQueryLevel2Test.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLQueryLevel2Test.java
index 598c7e2..e340eb6 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLQueryLevel2Test.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SQLQueryLevel2Test.java
@@ -16,7 +16,6 @@
  */
 package org.apache.jackrabbit.test.api.query;
 
-import javax.jcr.query.Query;
 import javax.jcr.query.QueryResult;
 import javax.jcr.query.RowIterator;
 import javax.jcr.query.Row;
@@ -137,7 +136,7 @@ public class SQLQueryLevel2Test extends AbstractQueryLevel2Test {
         tmp.append(" WHERE CONTAINS(., '\"quick brown\" -cat')");
         tmp.append(" AND ").append(jcrPath).append(" LIKE '");
         tmp.append(testRoot).append("/%'");
-        return new Statement(tmp.toString(), Query.SQL);
+        return new Statement(tmp.toString(), qsSQL);
     }
 
     /**
@@ -154,7 +153,7 @@ public class SQLQueryLevel2Test extends AbstractQueryLevel2Test {
         tmp.append(escapeIdentifierForSQL(propertyName1));
         tmp.append(" AND ").append(jcrPath).append(" LIKE '");
         tmp.append(testRoot).append("/%'");
-        return new Statement(tmp.toString(), Query.SQL);
+        return new Statement(tmp.toString(), qsSQL);
     }
 
     /**
@@ -171,6 +170,6 @@ public class SQLQueryLevel2Test extends AbstractQueryLevel2Test {
         tmp.append(" > 'a'");
         tmp.append(" AND ").append(jcrPath).append(" LIKE '");
         tmp.append(testRoot).append("/%'");
-        return new Statement(tmp.toString(), Query.SQL);
+        return new Statement(tmp.toString(), qsSQL);
     }
 }
\ No newline at end of file
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SaveTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SaveTest.java
index 3da1cb3..03f1a4f 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SaveTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SaveTest.java
@@ -134,7 +134,7 @@ public class SaveTest extends AbstractJCRTest {
         Node versionable = testRootNode.addNode(nodeName1, testNodeType);
         // or try to make it versionable if it is not
         ensureMixinType(versionable, mixVersionable);
-        testRootNode.save();
+        testRootNode.getSession().save();
         versionable.checkin();
 
         try {
@@ -157,9 +157,10 @@ public class SaveTest extends AbstractJCRTest {
     public void testConstraintViolationException() throws RepositoryException, NotExecutableException {
         checkNtQuery();
         Query query = superuser.getWorkspace().getQueryManager().createQuery(statement, Query.XPATH);
-        testRootNode.addNode(nodeName1, testNodeType);
+        testRootNode.addNode(nodeName1, testNodeTypeNoChildren);
         try {
             query.storeAsNode(testRoot + "/" + nodeName1 + "/" + nodeName2);
+            superuser.save();
             fail("Query.storeAsNode() must throw ConstraintViolationException, parent node does not allow child nodes.");
         } catch (ConstraintViolationException e) {
             // expected behaviour
@@ -186,7 +187,7 @@ public class SaveTest extends AbstractJCRTest {
         Node lockable = testRootNode.addNode(nodeName1, testNodeType);
         // or try to make it lockable if it is not
         ensureMixinType(lockable, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
         lockable.lock(false, true);
 
         Session readWrite = getHelper().getReadWriteSession();
@@ -211,7 +212,7 @@ public class SaveTest extends AbstractJCRTest {
         checkNtQuery();
         Query query = superuser.getWorkspace().getQueryManager().createQuery(statement, Query.XPATH);
         try {
-            query.storeAsNode(testRoot + "/invalid[path");
+            query.storeAsNode(testRoot + "/invalid[42]");
             fail("Query.storeAsNode() must throw RepositoryException on malformed path.");
         } catch (RepositoryException e) {
             // expected behaviour
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SimpleSelectionTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SimpleSelectionTest.java
index 250adc9..87bf3f8 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SimpleSelectionTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/SimpleSelectionTest.java
@@ -78,7 +78,7 @@ public class SimpleSelectionTest extends AbstractQueryTest {
         String propQuery = "/" + jcrRoot + firstChildpath + "[@" + jcrPrimaryType + "]";
 
         // execute search query
-        Query query = session.getWorkspace().getQueryManager().createQuery(propQuery, Query.XPATH);
+        Query query = session.getWorkspace().getQueryManager().createQuery(propQuery, qsXPATH);
         QueryResult result = query.execute();
 
         assertEquals("Should have only 1 result", 1, getSize(result.getRows()));
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/TextNodeTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/TextNodeTest.java
index d04e93d..383b2b7 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/TextNodeTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/TextNodeTest.java
@@ -64,7 +64,7 @@ public class TextNodeTest extends AbstractQueryTest {
     public void testTextNodeTest() throws RepositoryException, NotExecutableException {
         Node text1 = testRootNode.addNode(jcrXMLText);
         text1.setProperty(jcrXMLCharacters, "foo");
-        testRootNode.save();
+        testRootNode.getSession().save();
         String xpath = "/" + jcrRoot + testRoot + "/text()";
         executeXPathQuery(superuser, xpath, new Node[]{text1});
     }
@@ -79,7 +79,7 @@ public class TextNodeTest extends AbstractQueryTest {
         text1.setProperty(jcrXMLCharacters, "foo");
         Node text2 = testRootNode.addNode(nodeName1, testNodeType).addNode(jcrXMLText);
         text2.setProperty(jcrXMLCharacters, "foo");
-        testRootNode.save();
+        testRootNode.getSession().save();
         String xpath = "/" + jcrRoot + testRoot + "//text()";
         executeXPathQuery(superuser, xpath, new Node[]{text1, text2});
     }
@@ -94,7 +94,7 @@ public class TextNodeTest extends AbstractQueryTest {
         text1.setProperty(jcrXMLCharacters, "the quick brown fox jumps over the lazy dog.");
         Node text2 = testRootNode.addNode(nodeName1, testNodeType).addNode(jcrXMLText);
         text2.setProperty(jcrXMLCharacters, "java content repository");
-        testRootNode.save();
+        testRootNode.getSession().save();
         String xpath = "/" + jcrRoot + testRoot + "//text()[" + jcrContains + "(., 'fox')]";
         executeXPathQuery(superuser, xpath, new Node[]{text1});
     }
@@ -118,7 +118,7 @@ public class TextNodeTest extends AbstractQueryTest {
         testRootNode.addNode(nodeName1, testNodeType);
         Node text2 = testRootNode.addNode(jcrXMLText);
         text2.setProperty(jcrXMLCharacters, "foo");
-        testRootNode.save();
+        testRootNode.getSession().save();
         String xpath = "/" + jcrRoot + testRoot + "/text()[2]";
         executeXPathQuery(superuser, xpath, new Node[]{text2});
         xpath = "/" + jcrRoot + testRoot + "/text()[last()]";
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathDocOrderTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathDocOrderTest.java
index f9537d9..a55d0b2 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathDocOrderTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathDocOrderTest.java
@@ -21,7 +21,6 @@ import org.apache.jackrabbit.test.NotExecutableException;
 import javax.jcr.RepositoryException;
 import javax.jcr.NodeIterator;
 import javax.jcr.Repository;
-import javax.jcr.query.Query;
 import javax.jcr.query.QueryResult;
 
 /**
@@ -58,7 +57,7 @@ public class XPathDocOrderTest extends AbstractQueryTest {
         for (NodeIterator nodes = testRootNode.getNodes(); nodes.hasNext() && nodes.getPosition() < 2;) {
             resultPath = nodes.nextNode().getPath();
         }
-        docOrderTest(new Statement(xpath, Query.XPATH), resultPath);
+        docOrderTest(new Statement(xpath, qsXPATH), resultPath);
     }
 
     /**
@@ -73,7 +72,7 @@ public class XPathDocOrderTest extends AbstractQueryTest {
         for (NodeIterator nodes = testRootNode.getNodes(); nodes.hasNext() && nodes.getPosition() < 2;) {
             resultPath = nodes.nextNode().getPath();
         }
-        docOrderTest(new Statement(xpath, Query.XPATH), resultPath);
+        docOrderTest(new Statement(xpath, qsXPATH), resultPath);
     }
 
     /**
@@ -87,7 +86,7 @@ public class XPathDocOrderTest extends AbstractQueryTest {
         for (NodeIterator nodes = testRootNode.getNodes(); nodes.hasNext();) {
             resultPath = nodes.nextNode().getPath();
         }
-        docOrderTest(new Statement(xpath, Query.XPATH), resultPath);
+        docOrderTest(new Statement(xpath, qsXPATH), resultPath);
     }
 
     /**
@@ -98,7 +97,7 @@ public class XPathDocOrderTest extends AbstractQueryTest {
     public void testDocOrderFirstFunction() throws Exception {
         String xpath = xpathRoot + "/*[first()]";
         String resultPath = testRootNode.getNodes().nextNode().getPath();
-        docOrderTest(new Statement(xpath, Query.XPATH), resultPath);
+        docOrderTest(new Statement(xpath, qsXPATH), resultPath);
     }
 
     //-----------------------------< internal >---------------------------------
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathJcrPathTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathJcrPathTest.java
index d19fe67..d030fc5 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathJcrPathTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathJcrPathTest.java
@@ -66,7 +66,7 @@ public class XPathJcrPathTest extends AbstractQueryTest {
         String queryStatement = "//element(*, " + nodeTypeName + ")";
 
         // execute the search query
-        Query query = session.getWorkspace().getQueryManager().createQuery(queryStatement, Query.XPATH);
+        Query query = session.getWorkspace().getQueryManager().createQuery(queryStatement, qsXPATH);
         QueryResult result = query.execute();
 
         assertTrue("jcr:path must be present in query result row",
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathOrderByTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathOrderByTest.java
index 8d6d4b8..2830c6b 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathOrderByTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathOrderByTest.java
@@ -16,8 +16,6 @@
  */
 package org.apache.jackrabbit.test.api.query;
 
-import javax.jcr.query.Query;
-
 /**
  * This test searches for all nodes of a specific node type and orders them by
  * the property with name configured as {@link #propertyName1}.
@@ -54,7 +52,7 @@ public class XPathOrderByTest extends AbstractQueryTest {
      * For configuration description see {@link XPathOrderByTest}.
      */
     public void testOrderByAscending() throws Exception {
-        Statement stmt = new Statement(baseStatement + " ascending", Query.XPATH);
+        Statement stmt = new Statement(baseStatement + " ascending", qsXPATH);
         evaluateResultOrder(execute(stmt), propertyName1, false);
     }
 
@@ -64,7 +62,7 @@ public class XPathOrderByTest extends AbstractQueryTest {
      * For configuration description see {@link XPathOrderByTest}.
      */
     public void testOrderByDescending() throws Exception {
-        Statement stmt = new Statement(baseStatement + " descending", Query.XPATH);
+        Statement stmt = new Statement(baseStatement + " descending", qsXPATH);
         evaluateResultOrder(execute(stmt), propertyName1, true);
     }
 
@@ -74,7 +72,7 @@ public class XPathOrderByTest extends AbstractQueryTest {
      * For configuration description see {@link XPathOrderByTest}.
      */
     public void testOrderBy() throws Exception {
-        Statement stmt = new Statement(baseStatement, Query.XPATH);
+        Statement stmt = new Statement(baseStatement, qsXPATH);
         evaluateResultOrder(execute(stmt), propertyName1, false);
     }
 
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathPosIndexTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathPosIndexTest.java
index a67dfc9..aa66a94 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathPosIndexTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathPosIndexTest.java
@@ -21,7 +21,6 @@ import org.apache.jackrabbit.test.NotExecutableException;
 import javax.jcr.RepositoryException;
 import javax.jcr.NodeIterator;
 import javax.jcr.Repository;
-import javax.jcr.query.Query;
 import javax.jcr.query.QueryResult;
 
 /**
@@ -57,7 +56,7 @@ public class XPathPosIndexTest extends AbstractQueryTest {
         String path = testRoot + "/" + nodeName1 + "[2]";
         StringBuffer tmp = new StringBuffer("/");
         tmp.append(jcrRoot).append(path);
-        docOrderTest(new Statement(tmp.toString(), Query.XPATH), path);
+        docOrderTest(new Statement(tmp.toString(), qsXPATH), path);
     }
 
     //-----------------------------< internal >---------------------------------
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathQueryLevel2Test.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathQueryLevel2Test.java
index 675062b..3b34970 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathQueryLevel2Test.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/XPathQueryLevel2Test.java
@@ -16,7 +16,6 @@
  */
 package org.apache.jackrabbit.test.api.query;
 
-import javax.jcr.query.Query;
 import javax.jcr.query.QueryResult;
 import javax.jcr.query.RowIterator;
 import javax.jcr.Value;
@@ -131,7 +130,7 @@ public class XPathQueryLevel2Test extends AbstractQueryLevel2Test {
     private Statement getFullTextStatement() {
         String xpath =
             xpathRoot + "/*[" + jcrContains + "(., '\"quick brown\" -cat')]";
-        return new Statement(xpath, Query.XPATH);
+        return new Statement(xpath, qsXPATH);
     }
 
     /**
@@ -142,7 +141,7 @@ public class XPathQueryLevel2Test extends AbstractQueryLevel2Test {
         String xpath =
             xpathRoot + "/*[@" + propertyName2 + " = 'two' and @"
             + propertyName1 + " = 'existence']";
-        return new Statement(xpath, Query.XPATH);
+        return new Statement(xpath, qsXPATH);
     }
 
     /**
@@ -152,7 +151,7 @@ public class XPathQueryLevel2Test extends AbstractQueryLevel2Test {
         String xpath =
             xpathRoot + "/*[@" + propertyName1 + " <= 'b' and @"
             + propertyName1 + " > 'a']";
-        return new Statement(xpath, Query.XPATH);
+        return new Statement(xpath, qsXPATH);
     }
 
 }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/qom/ColumnTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/qom/ColumnTest.java
index c02a5a8..4b3a47f 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/qom/ColumnTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/qom/ColumnTest.java
@@ -20,6 +20,8 @@ import java.util.List;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Iterator;
+import java.util.HashSet;
+import java.util.Set;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.Node;
@@ -47,11 +49,11 @@ public class ColumnTest extends AbstractQOMTest {
 
     /**
      * From the spec:
-     * <p/>
+     * <p>
      * If propertyName is not specified, a column is included for each
      * single-valued non-residual property of the node type specified by the
      * nodeType attribute of the selector selectorName.
-     * <p/>
+     * <p>
      * [..] If propertyName is not specified,
      * columnName must not be specified, and the included columns will be
      * named "selectorName.propertyName".
@@ -65,21 +67,21 @@ public class ColumnTest extends AbstractQOMTest {
         forQOMandSQL2(qom, new Callable() {
             public Object call(Query query) throws RepositoryException {
                 QueryResult result = query.execute();
-                List<String> names = new ArrayList<String>(Arrays.asList(result.getColumnNames()));
                 NodeTypeManager ntMgr = superuser.getWorkspace().getNodeTypeManager();
                 NodeType nt = ntMgr.getNodeType(testNodeType);
                 PropertyDefinition[] propDefs = nt.getPropertyDefinitions();
+                Set<String> names = new HashSet<String>();
                 for (int i = 0; i < propDefs.length; i++) {
                     PropertyDefinition propDef = propDefs[i];
                     if (!propDef.isMultiple() && !propDef.getName().equals("*")) {
                         String columnName = SELECTOR_1 + "." + propDef.getName();
-                        assertTrue("Missing column: " + columnName,
-                                names.remove(columnName));
+                        names.add(columnName);
                     }
                 }
-                for (Iterator<String> it = names.iterator(); it.hasNext(); ) {
-                    fail(it.next() + " is not a property on node type " + testNodeType);
+                for (String columnName : result.getColumnNames()) {
+                    names.remove(columnName);
                 }
+                assertTrue("Missing required column(s): " + names, names.isEmpty());
                 return null;
             }
         });
@@ -87,7 +89,7 @@ public class ColumnTest extends AbstractQOMTest {
 
     /**
      * From the spec:
-     * <p/>
+     * <p>
      * If propertyName is specified, columnName is required and used to name
      * the column in the tabular results.
      */
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/qom/EquiJoinConditionTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/qom/EquiJoinConditionTest.java
index 1106024..819eb9c 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/qom/EquiJoinConditionTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/qom/EquiJoinConditionTest.java
@@ -63,7 +63,7 @@ public class EquiJoinConditionTest extends AbstractJoinTest {
         JoinCondition c = qf.equiJoinCondition(
                 LEFT, propertyName1, RIGHT, propertyName2);
         QueryObjectModel qom = createQuery(QueryObjectModelConstants.JCR_JOIN_TYPE_RIGHT_OUTER, c);
-        checkQOM(qom, new Node[][]{{n1, n2}, {n2, n2}});
+        checkQOM(qom, new Node[][]{{null, n1}, {n1, n2}, {n2, n2}});
     }
 
     public void testRightOuterJoin2() throws RepositoryException {
@@ -85,6 +85,6 @@ public class EquiJoinConditionTest extends AbstractJoinTest {
         JoinCondition c = qf.equiJoinCondition(
                 LEFT, propertyName2, RIGHT, propertyName1);
         QueryObjectModel qom = createQuery(QueryObjectModelConstants.JCR_JOIN_TYPE_LEFT_OUTER, c);
-        checkQOM(qom, new Node[][]{{n2, n1}, {n2, n2}});
+        checkQOM(qom, new Node[][]{{n1, null}, {n2, n1}, {n2, n2}});
     }
 }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/qom/GetQueryTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/qom/GetQueryTest.java
index 20b7ccf..a7e1c81 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/qom/GetQueryTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/query/qom/GetQueryTest.java
@@ -49,20 +49,20 @@ public class GetQueryTest extends AbstractQOMTest {
         );
         queries.add(qom);
         queries.add(qm.createQuery(qom.getStatement(), Query.JCR_SQL2));
-        if (isSupportedLanguage(Query.XPATH)) {
+        if (isSupportedLanguage(qsXPATH)) {
             String xpath = testPath + "/element(*, " + testNodeType + ")";
-            queries.add(qm.createQuery(xpath, Query.XPATH));
+            queries.add(qm.createQuery(xpath, qsXPATH));
         }
-        if (isSupportedLanguage(Query.SQL)) {
+        if (isSupportedLanguage(qsSQL)) {
             String sql = "select * from " + testNodeType + " where jcr:path like '" + testRoot + "/%'";
-            queries.add(qm.createQuery(sql, Query.SQL));
+            queries.add(qm.createQuery(sql, qsSQL));
         }
         for (Iterator<Query> it = queries.iterator(); it.hasNext(); ) {
             Query q = it.next();
             String lang = q.getLanguage();
             checkResult(q.execute(), new Node[]{n});
 
-            Node stored = q.storeAsNode(testRoot + "/" + nodeName1);
+            Node stored = q.storeAsNode(testRoot + "/storedQuery");
             q = qm.getQuery(stored);
             assertEquals("language of stored query does not match", lang, q.getLanguage());
             checkResult(q.execute(), new Node[]{n});
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/retention/HoldEffectTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/retention/HoldEffectTest.java
index 60c6aca..f4ee6f2 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/retention/HoldEffectTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/retention/HoldEffectTest.java
@@ -219,7 +219,6 @@ public class HoldEffectTest extends AbstractRetentionTest {
     }
 
     private void assertNoEffect(Property target) throws RepositoryException {
-        Session s = target.getSession();
         target.setValue("test3");
         target.remove();
     }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/retention/HoldTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/retention/HoldTest.java
index cbf06d4..e466e52 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/retention/HoldTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/retention/HoldTest.java
@@ -282,7 +282,7 @@ public class HoldTest extends AbstractRetentionTest {
     public void testRemoveHoldOnLockedNode() throws NotExecutableException, RepositoryException {
         Node child = getLockedChildNode();
         Hold h = retentionMgr.addHold(child.getPath(), getHoldName(), false);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         javax.jcr.Session otherS = getHelper().getSuperuserSession();
         try {
@@ -313,7 +313,7 @@ public class HoldTest extends AbstractRetentionTest {
         checkSupportedOption(Repository.OPTION_LOCKING_SUPPORTED);
         Node child = testRootNode.addNode(nodeName2, testNodeType);
         ensureMixinType(child, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
         child.lock(false, true); // session-scoped lock clean upon superuser-logout.
         return child;
     }
@@ -386,7 +386,7 @@ public class HoldTest extends AbstractRetentionTest {
         checkSupportedOption(Repository.OPTION_VERSIONING_SUPPORTED);
         Node child = testRootNode.addNode(nodeName2, testNodeType);
         ensureMixinType(child, mixVersionable);
-        testRootNode.save();
+        testRootNode.getSession().save();
         return child;
     }
 
@@ -403,13 +403,11 @@ public class HoldTest extends AbstractRetentionTest {
     }
 
     public void testHoldIsDeep() throws RepositoryException, NotExecutableException {
-        String holdName = getHoldName();
         Hold h = retentionMgr.addHold(testNodePath, getHoldName(), false);
         assertEquals("Hold.isDeep() must reflect the specified flag.", false, h.isDeep());
     }
 
     public void testHoldIsDeep2() throws RepositoryException, NotExecutableException {
-        String holdName = getHoldName();
         Hold h = retentionMgr.addHold(testNodePath, getHoldName(), true);
         assertEquals("Hold.isDeep() must reflect the specified flag.", true, h.isDeep());
     }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/retention/RetentionPolicyEffectTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/retention/RetentionPolicyEffectTest.java
index c06bd05..0e2ce34 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/retention/RetentionPolicyEffectTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/retention/RetentionPolicyEffectTest.java
@@ -164,7 +164,6 @@ public class RetentionPolicyEffectTest extends AbstractRetentionTest {
     }
 
     private void assertNoEffect(Property target) throws RepositoryException {
-        Session s = target.getSession();
         target.setValue("test3");
         target.remove();
     }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/retention/RetentionPolicyTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/retention/RetentionPolicyTest.java
index 29a80d6..7214c12 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/retention/RetentionPolicyTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/retention/RetentionPolicyTest.java
@@ -280,7 +280,7 @@ public class RetentionPolicyTest extends AbstractRetentionTest {
     public void testRemoveRetentionPolicyOnLockedNode() throws NotExecutableException, RepositoryException {
         String childPath = getLockedChildNode().getPath();
         retentionMgr.setRetentionPolicy(childPath, getApplicableRetentionPolicy());
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         Session otherS = getHelper().getSuperuserSession();
         try {
@@ -306,7 +306,7 @@ public class RetentionPolicyTest extends AbstractRetentionTest {
         checkSupportedOption(Repository.OPTION_LOCKING_SUPPORTED);
         Node child = testRootNode.addNode(nodeName2, testNodeType);
         ensureMixinType(child, mixLockable);
-        testRootNode.save();
+        testRootNode.getSession().save();
         child.lock(false, true); // session-scoped lock clean upon superuser-logout.
         return child;
     }
@@ -373,7 +373,7 @@ public class RetentionPolicyTest extends AbstractRetentionTest {
         checkSupportedOption(Repository.OPTION_VERSIONING_SUPPORTED);
         Node child = testRootNode.addNode(nodeName2, testNodeType);
         ensureMixinType(child, mixVersionable);
-        testRootNode.save();
+        testRootNode.getSession().save();
         return child;
     }
 
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/security/RSessionAccessControlTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/security/RSessionAccessControlTest.java
index 2ab240c..85492f9 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/security/RSessionAccessControlTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/security/RSessionAccessControlTest.java
@@ -23,13 +23,12 @@ import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.Value;
 
-import org.apache.jackrabbit.test.AbstractJCRTest;
 import org.apache.jackrabbit.test.RepositoryStub;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /** <code>RSessionAccessControlTest</code>... */
-public class RSessionAccessControlTest extends AbstractJCRTest {
+public class RSessionAccessControlTest extends AbstractAccessControlTest {
 
     private static Logger log = LoggerFactory.getLogger(RSessionAccessControlTest.class);
 
@@ -44,7 +43,7 @@ public class RSessionAccessControlTest extends AbstractJCRTest {
         Value v = getJcrValue(superuser, RepositoryStub.PROP_PROP_VALUE1, RepositoryStub.PROP_PROP_TYPE1, "test");
         Property p = n.setProperty(propertyName1, v);
         testPropertyPath = p.getPath();
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         readOnlySession = getHelper().getReadOnlySession();
     }
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/util/ISO9075.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/util/ISO9075.java
index e6d2d4b..2e29aa5 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/util/ISO9075.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/util/ISO9075.java
@@ -148,7 +148,7 @@ public class ISO9075 {
      * Encodes the character <code>c</code> as a String in the following form:
      * <code>"_x" + hex value of c + "_"</code>. Where the hex value has
      * four digits if the character with possibly leading zeros.
-     * <p/>
+     * <p>
      * Example: ' ' (the space character) is encoded to: _x0020_
      * @param c the character to encode
      * @param b the encoded character is appended to <code>StringBuffer</code>
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/util/Text.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/util/Text.java
index b60ad2f..94d5ed0 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/util/Text.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/util/Text.java
@@ -271,7 +271,7 @@ public class Text {
      * The list of characters that are not encoded by the <code>escape()</code>
      * and <code>unescape()</code> METHODS. They contains the characters as
      * defined 'unreserved' in section 2.3 of the RFC 2396 'URI generic syntax':
-     * <p/>
+     * <p>
      * <pre>
      * unreserved  = alphanum | mark
      * mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
@@ -452,7 +452,7 @@ public class Text {
      * the characters it absolutely needs to in order to make the resulting
      * string a valid JCR name.
      * Use {@link #unescapeIllegalJcrChars(String)} for decoding.
-     * <p/>
+     * <p>
      * QName EBNF:<br>
      * <xmp>
      * simplename ::= onecharsimplename | twocharsimplename | threeormorecharname
@@ -510,7 +510,7 @@ public class Text {
 
     /**
      * Unescapes previously escaped jcr chars.
-     * <p/>
+     * <p>
      * Please note, that this does not exactly the same as the url related
      * {@link #unescape(String)}, since it handles the byte-encoding
      * differently.
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/AbstractMergeTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/AbstractMergeTest.java
index 9574853..b51c444 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/AbstractMergeTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/AbstractMergeTest.java
@@ -17,6 +17,7 @@
 package org.apache.jackrabbit.test.api.version;
 
 import javax.jcr.Node;
+import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.Workspace;
@@ -24,6 +25,7 @@ import javax.jcr.nodetype.NodeType;
 import javax.jcr.nodetype.NodeTypeManager;
 
 import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.jackrabbit.test.NotExecutableException;
 
 /**
  * <code>AbstractMergeTest</code> is the abstract base class for all merge
@@ -70,6 +72,8 @@ public abstract class AbstractMergeTest extends AbstractJCRTest {
     protected void setUp() throws Exception {
         super.setUp();
 
+        super.checkSupportedOption(Repository.OPTION_VERSIONING_SUPPORTED);
+
         NodeTypeManager ntm = superuser.getWorkspace().getNodeTypeManager();
 
         // versionable node type
@@ -133,5 +137,5 @@ public abstract class AbstractMergeTest extends AbstractJCRTest {
     }
 
     // initialize nodes
-    abstract void initNodes() throws RepositoryException;
+    abstract void initNodes() throws RepositoryException, NotExecutableException;
 }
\ No newline at end of file
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/AbstractOnParentVersionTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/AbstractOnParentVersionTest.java
index 405939c..74f4be6 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/AbstractOnParentVersionTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/AbstractOnParentVersionTest.java
@@ -50,7 +50,7 @@ public abstract class AbstractOnParentVersionTest extends AbstractVersionTest {
         if (pd.getOnParentVersion() != OPVAction) {
             fail("JCR Property at '"+p.getPath()+"' does not have the required OnParentVersion "+OnParentVersionAction.nameFromValue(OPVAction)+" definition.");
         }
-        testRootNode.save();
+        testRootNode.getSession().save();
     }
 
     protected void tearDown() throws Exception {
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/AbstractVersionTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/AbstractVersionTest.java
index 36887bc..3b5864e 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/AbstractVersionTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/AbstractVersionTest.java
@@ -23,6 +23,7 @@ import javax.jcr.nodetype.NodeTypeManager;
 import javax.jcr.nodetype.NoSuchNodeTypeException;
 import javax.jcr.nodetype.NodeType;
 import javax.jcr.Node;
+import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.version.VersionHistory;
 
@@ -43,6 +44,8 @@ public class AbstractVersionTest extends AbstractJCRTest {
     protected void setUp() throws Exception {
         super.setUp();
 
+        super.checkSupportedOption(Repository.OPTION_VERSIONING_SUPPORTED);
+
         NodeTypeManager ntMgr = superuser.getWorkspace().getNodeTypeManager();
 
         // assert that this repository support versioning
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/CheckinTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/CheckinTest.java
index 8909320..5802b28 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/CheckinTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/CheckinTest.java
@@ -47,6 +47,7 @@ public class CheckinTest extends AbstractVersionTest {
      *
      * @throws javax.jcr.RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testIsCheckedOut() throws RepositoryException {
         versionableNode.checkin();
         assertTrue("After calling Node.checkin() on a versionable node N, N.isCheckedOut() must return false", versionableNode.isCheckedOut() == false);
@@ -71,6 +72,7 @@ public class CheckinTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testCheckinRemovesPredecessorProperty() throws RepositoryException {
 
         versionableNode.checkin();
@@ -101,6 +103,7 @@ public class CheckinTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testPredecessorIsCopiedToNewVersion() throws RepositoryException {
 
         Value[] nPredecessorsValue = versionableNode.getProperty(jcrPredecessors).getValues();
@@ -134,6 +137,7 @@ public class CheckinTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testMultipleCheckinHasNoEffect() throws RepositoryException {
 
         Version v = versionableNode.checkin();
@@ -172,6 +176,7 @@ public class CheckinTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testBaseVersionAfterCheckin() throws RepositoryException {
         Version v = versionableNode.checkin();
         Value baseVersionRef = versionableNode.getProperty(jcrBaseVersion).getValue();
@@ -200,6 +205,7 @@ public class CheckinTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testCheckinWithPendingChanges() throws RepositoryException {
         try {
             // modify node without calling save()
@@ -237,6 +243,7 @@ public class CheckinTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testIsNotCheckedOut() throws RepositoryException {
         versionableNode.checkin();
         boolean isCheckedOut = versionableNode.isCheckedOut();
@@ -263,6 +270,7 @@ public class CheckinTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testCheckinCreatesNewVersion() throws RepositoryException {
 
         long initialNumberOfVersions = getNumberOfVersions(versionableNode.getVersionHistory());
@@ -293,6 +301,7 @@ public class CheckinTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testCheckinNonVersionableNode() throws RepositoryException {
         try {
             nonVersionableNode.checkin();
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/CheckoutTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/CheckoutTest.java
index 375783f..4d51d52 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/CheckoutTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/CheckoutTest.java
@@ -52,6 +52,7 @@ public class CheckoutTest extends AbstractVersionTest {
      * Test if Node.isCheckedOut() returns true, if the versionable node has
      * been checked out before.
      */
+    @SuppressWarnings("deprecation")
     public void testIsCheckedOut() throws RepositoryException {
         versionableNode.checkout();
         assertTrue("After calling Node.checkout() a versionable node N, N.isCheckedOut() must return true.", versionableNode.isCheckedOut());
@@ -127,6 +128,7 @@ public class CheckoutTest extends AbstractVersionTest {
     /**
      * Test calling Node.checkout() on a non-versionable node.
      */
+    @SuppressWarnings("deprecation")
     public void testCheckoutNonVersionableNode() throws RepositoryException {
         try {
             nonVersionableNode.checkout();
@@ -155,6 +157,7 @@ public class CheckoutTest extends AbstractVersionTest {
      * Test if Node.checkout() doesn't throw any exception if the versionable
      * node has been checked out before.
      */
+    @SuppressWarnings("deprecation")
     public void testCheckoutTwiceDoesNotThrow() throws RepositoryException {
         versionableNode.checkout();
         versionableNode.checkout();
@@ -176,6 +179,7 @@ public class CheckoutTest extends AbstractVersionTest {
      * Test if Node.checkout() copies the node's jcr:baseVersion to node's
      * jcr:predecessors property (no save required).
      */
+    @SuppressWarnings("deprecation")
     public void testCheckoutCopiesBaseValueToPredecessorProperty() throws RepositoryException {
         Value baseVersionValue = versionableNode.getProperty(jcrBaseVersion).getValue();
         versionableNode.checkout();
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/CopyTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/CopyTest.java
index c42541d..0fedbf9 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/CopyTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/CopyTest.java
@@ -23,8 +23,6 @@ import javax.jcr.version.VersionHistory;
 import javax.jcr.version.VersionManager;
 import javax.jcr.version.Version;
 
-import org.apache.jackrabbit.test.api.version.simple.AbstractVersionTest;
-
 /**
  * <code>CopyTest</code> checks if full versionable nodes are copied correctly:
  *
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/GetReferencesNodeTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/GetReferencesNodeTest.java
index 03e1921..64ae1e9 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/GetReferencesNodeTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/GetReferencesNodeTest.java
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.test.api.version;
 import javax.jcr.Node;
 import javax.jcr.PropertyIterator;
 import javax.jcr.PropertyType;
+import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 
 import org.apache.jackrabbit.test.AbstractJCRTest;
@@ -51,6 +52,8 @@ public class GetReferencesNodeTest extends AbstractJCRTest {
     protected void setUp() throws Exception {
         super.setUp();
 
+        super.checkSupportedOption(Repository.OPTION_VERSIONING_SUPPORTED);
+
         versionableNodeType = getProperty(PROP_VERSIONABLE_NODE_TYPE);
         if (versionableNodeType == null) {
             fail("Property '" + PROP_VERSIONABLE_NODE_TYPE + "' is not defined.");
@@ -78,13 +81,13 @@ public class GetReferencesNodeTest extends AbstractJCRTest {
         ensureCanSetProperty(testNode, propertyName1, PropertyType.REFERENCE, false);
         testNode.setProperty(propertyName1, nodeToBeReferenced);
 
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.checkin();
 
         // create a version 1.1 and remove reference
         testNode.checkout();
         testNode.getProperty(propertyName1).remove();
-        testRootNode.save();
+        testRootNode.getSession().save();
         testNode.checkin();
 
         // check if reference is returned
@@ -107,6 +110,6 @@ public class GetReferencesNodeTest extends AbstractJCRTest {
 
         // node to be referenced, does not have to be versionable
         nodeToBeReferenced = testRootNode.addNode(nodeName2, versionableNodeType);
-        testRootNode.save();
+        testRootNode.getSession().save();
     }
 }
\ No newline at end of file
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/MergeActivityTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/MergeActivityTest.java
index ef82799..f734d52 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/MergeActivityTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/MergeActivityTest.java
@@ -18,9 +18,11 @@ package org.apache.jackrabbit.test.api.version;
 
 import javax.jcr.Node;
 import javax.jcr.NodeIterator;
+import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.version.VersionManager;
 
+import org.apache.jackrabbit.test.NotExecutableException;
 /**
  * <code>MergeActivityTest</code> contains tests dealing with merging activities
  *
@@ -75,7 +77,9 @@ public class MergeActivityTest extends AbstractMergeTest {
     /**
      * initialize a versionable node on default and second workspace
      */
-    protected void initNodes() throws RepositoryException {
+    protected void initNodes() throws RepositoryException, NotExecutableException {
+
+        checkSupportedOption(Repository.OPTION_ACTIVITIES_SUPPORTED);
 
         VersionManager versionManager = testRootNode.getSession().getWorkspace().getVersionManager();
 
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/MergeCancelMergeTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/MergeCancelMergeTest.java
index 3fdc461..556824f 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/MergeCancelMergeTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/MergeCancelMergeTest.java
@@ -58,6 +58,7 @@ public class MergeCancelMergeTest extends AbstractMergeTest {
      * the jcr:mergeFailed property of N. <br> without adding it to
      * jcr:predecessors.<br> Branches will not be joined.<br>
      */
+    @SuppressWarnings("deprecation")
     public void testMergeNodeCancelMerge() throws RepositoryException {
         // create 2 independent versions for a node and its corresponding node
         // so merge fails for this node
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/MergeNodeTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/MergeNodeTest.java
index 4509f48..d3b13b8 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/MergeNodeTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/MergeNodeTest.java
@@ -67,6 +67,7 @@ public class MergeNodeTest extends AbstractMergeTest {
      * Node.merge(): InvalidItemStateException if unsaved changes within the
      * current Session<br>
      */
+    @SuppressWarnings("deprecation")
     public void testMergeNodeWithUnsavedStates() throws RepositoryException {
         // set a property and do not save workspace
         nodeToMerge.setProperty(propertyName1, CHANGED_STRING);
@@ -97,6 +98,7 @@ public class MergeNodeTest extends AbstractMergeTest {
     /**
      * Perform a merge on a node with a unkwnown workspacename
      */
+    @SuppressWarnings("deprecation")
     public void testMergeUnknownWorkspaceName() throws RepositoryException {
         try {
             nodeToMerge.merge(getNonExistingWorkspaceName(superuser), false);
@@ -122,6 +124,7 @@ public class MergeNodeTest extends AbstractMergeTest {
      * indicated workspace <br> then the merge method returns quietly and no
      * changes are made.<br>
      */
+    @SuppressWarnings("deprecation")
     public void testMergeNodeNonCorrespondingNode() throws RepositoryException {
         // create new node - this node has no corresponding node in default workspace
         Node subNode = nodeToMerge.addNode(nodeName3, versionableNodeType);
@@ -156,6 +159,7 @@ public class MergeNodeTest extends AbstractMergeTest {
      * successor of, predecessor of, nor identical with V', then the merge
      * result for N is failed<br>
      */
+    @SuppressWarnings("deprecation")
     public void testMergeNodeVersionAmbiguous() throws RepositoryException {
         // create 2 independent versions for a node and its corresponding node
         // so merge fails for this node
@@ -215,6 +219,7 @@ public class MergeNodeTest extends AbstractMergeTest {
      * Node.merge(): bestEffort is true > any merge-failure (represented by the
      * version in the workspace) is reported in the jcrMergeFailed property<br>
      */
+    @SuppressWarnings("deprecation")
     public void testMergeNodeBestEffortTrueCheckMergeFailedProperty() throws RepositoryException {
         // create 2 independent versions for a node and its corresponding node
         // so merge fails for this node
@@ -298,6 +303,7 @@ public class MergeNodeTest extends AbstractMergeTest {
     /**
      * if mergeFailedProperty is present > VersionException<br>
      */
+    @SuppressWarnings("deprecation")
     public void disable_testMergeNodeForceFailure() throws RepositoryException {
         // create 2 independent versions for a node and its corresponding node
         // so merge fails for this node
@@ -359,6 +365,7 @@ public class MergeNodeTest extends AbstractMergeTest {
      * Node.merge(): bestEffort is false and any merge fails a MergeException is
      * thrown.<br>
      */
+    @SuppressWarnings("deprecation")
     public void testMergeNodeBestEffortFalse() throws RepositoryException {
         /// create successor versions for a node
         // so merge fails for this node
@@ -409,6 +416,7 @@ public class MergeNodeTest extends AbstractMergeTest {
      * versionable node is encountered whose corresponding node's base version
      * is on a divergent branch from this node's base version.
      */
+    @SuppressWarnings("deprecation")
     public void testMergeNodeBestEffortFalseAmbiguousVersions() throws RepositoryException {
         /// create 2 independent base versions for a node and its corresponding node
         // so merge fails for this node
@@ -471,6 +479,7 @@ public class MergeNodeTest extends AbstractMergeTest {
      * locked node.
      * @throws NotExecutableException if repository does not support locking.
      */
+    @SuppressWarnings("deprecation")
     public void disable_testMergeLocked()
             throws NotExecutableException, RepositoryException {
 
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/MergeSubNodeTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/MergeSubNodeTest.java
index 8171983..707bb87 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/MergeSubNodeTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/MergeSubNodeTest.java
@@ -58,6 +58,7 @@ public class MergeSubNodeTest extends AbstractMergeTest {
      * update, then the subnode N is removed<br> retrieve the initialised node
      * to perform operations we need before for this test<br>
      */
+    @SuppressWarnings("deprecation")
     public void disable_testRemoveNodeFromSourceWorkspaceAndMergeWithUpdate() throws RepositoryException {
         // status 'update' for parent
         nodeToMerge.checkin();
@@ -123,6 +124,7 @@ public class MergeSubNodeTest extends AbstractMergeTest {
      * on the workspace1 and then merge the one in workspace2 with the one in
      * workspace1 precondition is that the node in workspace2 is checked in
      */
+    @SuppressWarnings("deprecation")
     public void disable_testMergeNodeFromUpdatedSourceWorkspace() throws RepositoryException {
         Node originalNode = testRootNode.getNode(nodeName1);
 
@@ -130,7 +132,7 @@ public class MergeSubNodeTest extends AbstractMergeTest {
         originalNode.checkout();
         originalNode.checkin();
 
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         // "merge" the clonedNode with the newNode from the default workspace
         // besteffort set to false to stop at the first failure
@@ -179,6 +181,7 @@ public class MergeSubNodeTest extends AbstractMergeTest {
      * workspace1<br> the node in workspace2 should be updated<br> precondition
      * is that the node in workspace2 is checked in
      */
+    @SuppressWarnings("deprecation")
     public void testMergeNodeFromOlderSourceWorkspace() throws RepositoryException {
         // touch the version on workspace2
         nodeToMerge.checkin();
@@ -223,6 +226,7 @@ public class MergeSubNodeTest extends AbstractMergeTest {
      * Node.merge(): bestEffort is true > (sub)node which could not be merged
      * are not affected.<br>
      */
+    @SuppressWarnings("deprecation")
     public void disable_testMergeNodeBestEffortTrue() throws RepositoryException {
         // create 2 new nodes with two independent versions
         // so merge fails for this node
@@ -301,6 +305,7 @@ public class MergeSubNodeTest extends AbstractMergeTest {
      * node,<br> a merge test is performed comparing N with its corresponding
      * node in workspace, N'.<br>
      */
+    @SuppressWarnings("deprecation")
     public void disable_testMergeNodeSubNodesMergeTest() throws RepositoryException {
         //setCheckProperty(nodeToMerge);
         nodeToMerge.checkout();
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/OnParentVersionAbortTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/OnParentVersionAbortTest.java
index c395128..1ef9b7d 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/OnParentVersionAbortTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/OnParentVersionAbortTest.java
@@ -80,7 +80,7 @@ public class OnParentVersionAbortTest extends AbstractOnParentVersionTest {
     public void testRestoreNode() throws RepositoryException, NotExecutableException {
         // create child node with OPV-ABORT behaviour
         addChildNode(OPVAction);
-        testRootNode.save();
+        testRootNode.getSession().save();
         try {
             versionableNode.checkin();
             fail("On checkin of N which has a child node with OnParentVersion ABORT defined, an UnsupportedRepositoryOperationException must be thrown.");
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/OnParentVersionCopyTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/OnParentVersionCopyTest.java
index d049a15..f449804 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/OnParentVersionCopyTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/OnParentVersionCopyTest.java
@@ -54,6 +54,7 @@ public class OnParentVersionCopyTest extends AbstractOnParentVersionTest {
      *
      * @throws javax.jcr.RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreNode() throws RepositoryException {
         // prepare for node test
         Node childNode = addChildNode(OPVAction);
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/RemoveVersionTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/RemoveVersionTest.java
index 9154f08..1db22f6 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/RemoveVersionTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/RemoveVersionTest.java
@@ -209,7 +209,7 @@ public class RemoveVersionTest extends AbstractVersionTest {
         Value refValue = superuser.getValueFactory().createValue(version);
         ensureCanSetProperty(n1, propertyName1, refValue);
         n1.setProperty(propertyName1, refValue);
-        testRootNode.save();
+        testRootNode.getSession().save();
 
         try {
             vHistory.removeVersion(version.getName());
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/RestoreTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/RestoreTest.java
index 26240d8..6aba3b5 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/RestoreTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/RestoreTest.java
@@ -100,6 +100,7 @@ public class RestoreTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreRootVersionFail() throws RepositoryException {
         try {
             versionableNode.restore(rootVersion, true);
@@ -128,6 +129,7 @@ public class RestoreTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreOnCheckedInNode() throws RepositoryException {
         versionableNode.checkin();
         versionableNode.restore(version, true);
@@ -178,6 +180,7 @@ public class RestoreTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreOnCheckedOutNode() throws RepositoryException {
         versionableNode.restore(version, true);
     }
@@ -223,6 +226,7 @@ public class RestoreTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreSetsIsCheckedOutToFalse() throws RepositoryException {
         versionableNode.restore(version, true);
         assertFalse("Restoring a node sets the jcr:isCheckedOut property to false", versionableNode.isCheckedOut());
@@ -273,6 +277,7 @@ public class RestoreTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreCorrectProperty() throws RepositoryException {
         versionableNode.restore(version, true);
         String value = versionableNode.getProperty(propertyName1).getString();
@@ -328,6 +333,7 @@ public class RestoreTest extends AbstractVersionTest {
      *
      * @throws javax.jcr.RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreSetsBaseVersion() throws RepositoryException {
         versionableNode.restore(version, true);
         Version baseV = versionableNode.getBaseVersion();
@@ -383,6 +389,7 @@ public class RestoreTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreWithPendingChanges() throws RepositoryException {
         // modify node without calling save()
         try {
@@ -469,6 +476,7 @@ public class RestoreTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreInvalidVersion() throws RepositoryException {
         Version vNode2 = versionableNode2.checkin();
         try {
@@ -502,6 +510,7 @@ public class RestoreTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreInvalidVersion2() throws RepositoryException {
         String invalidName;
         do {
@@ -555,6 +564,7 @@ public class RestoreTest extends AbstractVersionTest {
      * @throws RepositoryException
      * @see Node#restore(String, boolean)
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreNonVersionableNode() throws RepositoryException {
         try {
             nonVersionableNode.restore("foo", true);
@@ -602,6 +612,7 @@ public class RestoreTest extends AbstractVersionTest {
      * @throws RepositoryException
      * @see Node#restore(Version, boolean)
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreNonVersionableNode3() throws RepositoryException {
         try {
             nonVersionableNode.restore(version, true);
@@ -631,6 +642,7 @@ public class RestoreTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreWithInvalidVersion() throws RepositoryException {
         Version invalidVersion = versionableNode2.checkin();
         try {
@@ -660,6 +672,7 @@ public class RestoreTest extends AbstractVersionTest {
      * Tests if restoring the <code>Version</code> of an existing node throws an
      * <code>ItemExistsException</code> if removeExisting is set to FALSE.
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreWithUUIDConflict() throws RepositoryException, NotExecutableException {
         try {
             Node naa = createVersionableNode(versionableNode, nodeName4, versionableNodeType);
@@ -801,6 +814,7 @@ public class RestoreTest extends AbstractVersionTest {
         }
     }
 
+    @SuppressWarnings("deprecation")
     public void testRestoreRemoved() throws RepositoryException {
         Node parent = versionableNode.getParent();
         String oldName = versionableNode.getName();
@@ -916,6 +930,7 @@ public class RestoreTest extends AbstractVersionTest {
      * Test the restore of a versionable node using a label.
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreLabel() throws RepositoryException {
         // mark V1 with label test1
         versionableNode.getVersionHistory().addVersionLabel(version.getName(), "test", true);
@@ -944,6 +959,7 @@ public class RestoreTest extends AbstractVersionTest {
      * Test the restore of the OPV=Version child nodes.
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreName() throws RepositoryException,
             NotExecutableException {
         // V1.0 of versionableNode has no child
@@ -958,7 +974,7 @@ public class RestoreTest extends AbstractVersionTest {
 
         // create V1.1 of child
         child1.checkout();
-        Version v11Child = child1.checkin();
+        child1.checkin();
 
         // V2 of versionable node has child1
         versionableNode.checkout();
@@ -1002,14 +1018,14 @@ public class RestoreTest extends AbstractVersionTest {
         ensureMixinType(child1, mixVersionable);
         versionableNode.getSession().save();
         // create v1.0 of child
-        Version v1Child = child1.checkin();
+        Version v1Child = versionManager.checkin(child1.getPath());
 
         // V1 of versionable node has child1
         String v1 = versionManager.checkin(versionableNode.getPath()).getName();
 
         // create V1.1 of child
         versionManager.checkout(child1.getPath());
-        Version v11Child = versionManager.checkin(child1.getPath());
+        versionManager.checkin(child1.getPath());
 
         // V2 of versionable node has child1
         versionManager.checkout(versionableNode.getPath());
@@ -1025,16 +1041,22 @@ public class RestoreTest extends AbstractVersionTest {
         child1 = versionableNode.getNode(nodeName4);
         assertEquals("restore must restore child node version 1.0.", v1Child.getName(), versionManager.getBaseVersion(child1.getPath()).getName());
 
-        // restore V2 via name. child should be 1.1
+        // JSR283 is more clear about restoring versionable OPV=VERSION nodes
+        // and states that an existing one is not restored when the parent
+        // is restored (see 15.7.5 Chained Versions on Restore)
+
+        // New JSR283 version:
+        // restore V2 via name. child should still be be 1.0
         versionManager.restore(versionableNode.getPath(), v2, true);
         child1 = versionableNode.getNode(nodeName4);
-        assertEquals("Node.restore('foo') must restore child node version 1.1.", v11Child.getName(), versionManager.getBaseVersion(child1.getPath()).getName());
+        assertEquals("Node.restore('foo') must not restore child node and keep version 1.0.", v1Child.getName(), versionManager.getBaseVersion(child1.getPath()).getName());
     }
 
     /**
      * Test the child ordering of restored nodes.
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreOrder() throws RepositoryException,
             NotExecutableException {
         // create a test-root that has orderable child nodes
@@ -1240,6 +1262,7 @@ public class RestoreTest extends AbstractVersionTest {
      * Test the child ordering of restored nodes.
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreOrder2() throws RepositoryException,
             NotExecutableException {
         // create a test-root that has orderable child nodes
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/SessionMoveVersionExceptionTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/SessionMoveVersionExceptionTest.java
index 86f9836..5c2955f 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/SessionMoveVersionExceptionTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/SessionMoveVersionExceptionTest.java
@@ -45,7 +45,7 @@ public class SessionMoveVersionExceptionTest extends AbstractVersionTest {
         try {
             // try to move the sub node this should throw an VersionException
             // either instantly or upon save()
-            superuser.move(movingNode.getPath(), nonVersionableNode.getPath());
+            superuser.move(movingNode.getPath(), nonVersionableNode.getPath() + "/" + nodeName1);
             superuser.save();
             fail("Moving a node using Session.move() where parent node is " +
                     "versionable and checked in should throw a VersionException!");
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/VersionHistoryTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/VersionHistoryTest.java
index b22d6ac..5b8261c 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/VersionHistoryTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/VersionHistoryTest.java
@@ -187,6 +187,7 @@ public class VersionHistoryTest extends AbstractVersionTest {
      *
      * @see javax.jcr.version.VersionHistory#getAllVersions()
      */
+    @SuppressWarnings("deprecation")
     public void testGetAllVersions() throws RepositoryException {
         int cnt = 5;
         Map<String, Version> versions = new HashMap<String, Version>();
@@ -219,10 +220,13 @@ public class VersionHistoryTest extends AbstractVersionTest {
     public void testGetAllVersionsJcr2() throws RepositoryException {
         int cnt = 5;
         Map<String, Version> versions = new HashMap<String, Version>();
+        List<String> vnames = new ArrayList<String>();
         Version v = vHistory.getRootVersion();
         versions.put(v.getIdentifier(), v);
+        vnames.add(v.getIdentifier());
         for (int i = 0; i < cnt; i++) {
             v = versionManager.checkin(versionableNode.getPath());
+            vnames.add(v.getIdentifier());
             versions.put(v.getIdentifier(), v);
             versionManager.checkout(versionableNode.getPath());
         }
@@ -234,8 +238,10 @@ public class VersionHistoryTest extends AbstractVersionTest {
                 fail("VersionHistory.getAllVersions() must only contain the root version and versions, that have been created by a Node.checkin() call.");
             }
             versions.remove(v.getIdentifier());
+            // check order of linear version history (see JCR 2.0, 15.1.1.2)
+            assertEquals("versions in a linear version history should be sorted by creation time", vnames.remove(0), v.getIdentifier());
         }
-        assertTrue("VersionHistory.getAllVersions() must contain the root version and all versions that have been created with a Node.checkin() call.", versions.isEmpty());
+        assertTrue("VersionHistory.getAllVersions() must only contain the root version and all versions that have been created with a Node.checkin() call.", versions.isEmpty());
     }
 
     /**
@@ -278,6 +284,7 @@ public class VersionHistoryTest extends AbstractVersionTest {
      * Test if UnsupportedRepositoryOperationException is thrown when calling
      * Node.getVersionHistory() on a non-versionable node.
      */
+    @SuppressWarnings("deprecation")
     public void testGetVersionHistoryOnNonVersionableNode() throws RepositoryException {
         try {
             nonVersionableNode.getVersionHistory();
@@ -368,6 +375,7 @@ public class VersionHistoryTest extends AbstractVersionTest {
      * Tests if <code>VersionHistory.cancelMerge(Version)</code> throws an
      * {@link javax.jcr.UnsupportedRepositoryOperationException}
      */
+    @SuppressWarnings("deprecation")
     public void testCancelMerge() throws Exception {
         try {
             vHistory.cancelMerge(version);
@@ -392,6 +400,7 @@ public class VersionHistoryTest extends AbstractVersionTest {
      * Tests if <code>VersionHistory.checkin()</code> throws an {@link
      * javax.jcr.UnsupportedRepositoryOperationException}
      */
+    @SuppressWarnings("deprecation")
     public void testCheckin() throws Exception {
         try {
             vHistory.checkin();
@@ -416,6 +425,7 @@ public class VersionHistoryTest extends AbstractVersionTest {
      * Tests if <code>VersionHistory.checkout()</code> throws an {@link
      * javax.jcr.UnsupportedRepositoryOperationException}
      */
+    @SuppressWarnings("deprecation")
     public void testCheckout() throws Exception {
         try {
             vHistory.checkout();
@@ -440,6 +450,7 @@ public class VersionHistoryTest extends AbstractVersionTest {
      * Tests if <code>VersionHistory.doneMerge(Version)</code> throws an {@link
      * javax.jcr.UnsupportedRepositoryOperationException}
      */
+    @SuppressWarnings("deprecation")
     public void testDoneMerge() throws Exception {
         try {
             vHistory.doneMerge(version);
@@ -472,6 +483,7 @@ public class VersionHistoryTest extends AbstractVersionTest {
      * Tests if <code>VersionHistory.getBaseVersion()</code> throws an {@link
      * javax.jcr.UnsupportedRepositoryOperationException}
      */
+    @SuppressWarnings("deprecation")
     public void testGetBaseVersion() throws Exception {
         try {
             vHistory.getBaseVersion();
@@ -518,6 +530,7 @@ public class VersionHistoryTest extends AbstractVersionTest {
      * Tests if <code>VersionHistory.getLock()</code> throws an {@link
      * javax.jcr.lock.LockException}
      */
+    @SuppressWarnings("deprecation")
     public void testGetLock() throws Exception {
         try {
             vHistory.getLock();
@@ -826,6 +839,7 @@ public class VersionHistoryTest extends AbstractVersionTest {
      * Tests if <code>VersionHistory.lock(boolean, boolean)</code> throws a
      * {@link javax.jcr.lock.LockException}
      */
+    @SuppressWarnings("deprecation")
     public void testLock() throws Exception {
         try {
             vHistory.lock(true, true);
@@ -1006,6 +1020,7 @@ public class VersionHistoryTest extends AbstractVersionTest {
      * Tests if <code>VersionHistory.restoreByLabel(String, boolean)</code>
      * throws an {@link javax.jcr.UnsupportedRepositoryOperationException}
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreByLabel() throws Exception {
         try {
             vHistory.restoreByLabel("abc", true);
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/VersionLabelTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/VersionLabelTest.java
index 06c9461..63bda3a 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/VersionLabelTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/VersionLabelTest.java
@@ -52,7 +52,7 @@ public class VersionLabelTest extends AbstractVersionTest {
     protected String versionLabel2 = "bar";
 
     protected VersionHistory vHistory;
-    protected Version rootVersion;
+    protected Version version;
 
     /**
      * JCR Name jcr:versionLabels using the namespace resolver of the current session.
@@ -65,7 +65,9 @@ public class VersionLabelTest extends AbstractVersionTest {
         jcrVersionLabels = superuser.getNamespacePrefix(NS_JCR_URI) + ":versionLabels";
 
         vHistory = versionableNode.getSession().getWorkspace().getVersionManager().getVersionHistory(versionableNode.getPath());
-        rootVersion = vHistory.getRootVersion();
+        VersionManager vMgr = superuser.getWorkspace().getVersionManager();
+        vMgr.checkpoint(versionableNode.getPath());
+        version = vMgr.getBaseVersion(versionableNode.getPath());
 
         if (vHistory.hasVersionLabel(versionLabel)) {
             fail("Version label '" + versionLabel + "' is already present in this version history. Label test cannot be performed.");
@@ -85,7 +87,7 @@ public class VersionLabelTest extends AbstractVersionTest {
             // ignore
         }
         vHistory = null;
-        rootVersion = null;
+        version = null;
         super.tearDown();
     }
 
@@ -98,7 +100,7 @@ public class VersionLabelTest extends AbstractVersionTest {
      */
     public void testAddVersionLabel() throws RepositoryException {
         int initialLength = vHistory.getVersionLabels().length;
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel, false);
+        vHistory.addVersionLabel(version.getName(), versionLabel, false);
         String[] labels = vHistory.getVersionLabels();
 
         assertEquals("A version label that has been successfully added must increes the total number of version labels available in the history.", initialLength + 1, labels.length);
@@ -113,7 +115,7 @@ public class VersionLabelTest extends AbstractVersionTest {
      * @see VersionHistory#addVersionLabel(String, String, boolean)
      */
     public void testAddVersionLabel2() throws RepositoryException {
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel, false);
+        vHistory.addVersionLabel(version.getName(), versionLabel, false);
         String[] labels = vHistory.getVersionLabels();
         boolean found = false;
         for (int i = 0; i < labels.length; i++) {
@@ -133,14 +135,15 @@ public class VersionLabelTest extends AbstractVersionTest {
      *
      * @see VersionHistory#addVersionLabel(String, String, boolean)
      */
+    @SuppressWarnings("deprecation")
     public void testAddVersionCheckVersionLabelsNode() throws RepositoryException {
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel, false);
+        vHistory.addVersionLabel(version.getName(), versionLabel, false);
 
         // get jcr:versionLabels node
         vHistory = versionableNode.getVersionHistory();
         Node versionLabelNode = vHistory.getNode(jcrVersionLabels);
 
-        assertTrue("The version label that has been successfully added must be present in the node '" + jcrVersionLabels + "'.", versionLabelNode.getProperty(versionLabel).getString().equals(rootVersion.getUUID()));
+        assertTrue("The version label that has been successfully added must be present in the node '" + jcrVersionLabels + "'.", versionLabelNode.getProperty(versionLabel).getString().equals(version.getUUID()));
     }
 
     /**
@@ -152,13 +155,13 @@ public class VersionLabelTest extends AbstractVersionTest {
      * @see VersionHistory#addVersionLabel(String, String, boolean)
      */
     public void testAddVersionCheckVersionLabelsNodeJcr2() throws RepositoryException {
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel, false);
+        vHistory.addVersionLabel(version.getName(), versionLabel, false);
 
         // get jcr:versionLabels node
         vHistory = versionableNode.getSession().getWorkspace().getVersionManager().getVersionHistory(versionableNode.getPath());
         Node versionLabelNode = vHistory.getNode(jcrVersionLabels);
 
-        assertTrue("The version label that has been successfully added must be present in the node '" + jcrVersionLabels + "'.", versionLabelNode.getProperty(versionLabel).getString().equals(rootVersion.getUUID()));
+        assertTrue("The version label that has been successfully added must be present in the node '" + jcrVersionLabels + "'.", versionLabelNode.getProperty(versionLabel).getString().equals(version.getUUID()));
     }
 
     /**
@@ -169,7 +172,7 @@ public class VersionLabelTest extends AbstractVersionTest {
      * @see VersionHistory#hasVersionLabel(String)
      */
     public void testHasVersionLabel() throws RepositoryException {
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel, false);
+        vHistory.addVersionLabel(version.getName(), versionLabel, false);
         assertTrue("VersionHistory.hasVersionLabel(String) must return true if the label has been sucessfully added.", vHistory.hasVersionLabel(versionLabel));
     }
 
@@ -181,8 +184,8 @@ public class VersionLabelTest extends AbstractVersionTest {
      * @see VersionHistory#hasVersionLabel(javax.jcr.version.Version, String)
      */
     public void testHasVersionLabelForVersion() throws RepositoryException {
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel, false);
-        assertTrue("VersionHistory.hasVersionLabel(Version, String) must return true if the label has been sucessfully added.", vHistory.hasVersionLabel(rootVersion, versionLabel));
+        vHistory.addVersionLabel(version.getName(), versionLabel, false);
+        assertTrue("VersionHistory.hasVersionLabel(Version, String) must return true if the label has been sucessfully added.", vHistory.hasVersionLabel(version, versionLabel));
     }
 
     /**
@@ -192,8 +195,8 @@ public class VersionLabelTest extends AbstractVersionTest {
      */
     public void testAddMultipleVersionLabels() throws RepositoryException {
         try {
-            vHistory.addVersionLabel(rootVersion.getName(), versionLabel, false);
-            vHistory.addVersionLabel(rootVersion.getName(), versionLabel2, false);
+            vHistory.addVersionLabel(version.getName(), versionLabel, false);
+            vHistory.addVersionLabel(version.getName(), versionLabel2, false);
         } catch (VersionException e) {
             fail("Adding multiple distict version labels to a version must be allowed.");
         }
@@ -205,8 +208,9 @@ public class VersionLabelTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testAddDuplicateVersionLabel() throws RepositoryException {
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel, false);
+        vHistory.addVersionLabel(version.getName(), versionLabel, false);
         try {
             versionableNode.checkout();
             Version v = versionableNode.checkin();
@@ -225,7 +229,7 @@ public class VersionLabelTest extends AbstractVersionTest {
      * @throws RepositoryException
      */
     public void testAddDuplicateVersionLabelJcr2() throws RepositoryException {
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel, false);
+        vHistory.addVersionLabel(version.getName(), versionLabel, false);
         try {
             VersionManager versionManager = versionableNode.getSession().getWorkspace().getVersionManager();
             String path = versionableNode.getPath();
@@ -245,8 +249,9 @@ public class VersionLabelTest extends AbstractVersionTest {
      * @throws RepositoryException
      * @see VersionHistory#addVersionLabel(String, String, boolean)  with boolan flag equals true.
      */
+    @SuppressWarnings("deprecation")
     public void testMoveLabel() throws RepositoryException {
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel, false);
+        vHistory.addVersionLabel(version.getName(), versionLabel, false);
         try {
             versionableNode.checkout();
             Version v = versionableNode.checkin();
@@ -268,7 +273,7 @@ public class VersionLabelTest extends AbstractVersionTest {
      * @see VersionHistory#addVersionLabel(String, String, boolean)  with boolan flag equals true.
      */
     public void testMoveLabelJcr2() throws RepositoryException {
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel, false);
+        vHistory.addVersionLabel(version.getName(), versionLabel, false);
         try {
             VersionManager versionManager = versionableNode.getSession().getWorkspace().getVersionManager();
             String path = versionableNode.getPath();
@@ -311,7 +316,7 @@ public class VersionLabelTest extends AbstractVersionTest {
      */
     public void testRemoveLabel() throws RepositoryException {
 
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel, false);
+        vHistory.addVersionLabel(version.getName(), versionLabel, false);
         vHistory.removeVersionLabel(versionLabel);
 
         assertFalse("VersionHistory.removeLabel(String) must remove the version label if it exists (has successfully been added before).", vHistory.hasVersionLabel(versionLabel));
@@ -325,10 +330,10 @@ public class VersionLabelTest extends AbstractVersionTest {
      * @see VersionHistory#getVersionByLabel(String)
      */
     public void testGetVersionByLabel() throws RepositoryException {
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel, true);
+        vHistory.addVersionLabel(version.getName(), versionLabel, true);
         Version v = vHistory.getVersionByLabel(versionLabel);
 
-        assertTrue("VersionHistory.getVersionByLabel(String) must retrieve the particular version that was specified in addVersionLabel call.", v.isSame(rootVersion));
+        assertTrue("VersionHistory.getVersionByLabel(String) must retrieve the particular version that was specified in addVersionLabel call.", v.isSame(version));
     }
 
     /**
@@ -337,6 +342,7 @@ public class VersionLabelTest extends AbstractVersionTest {
      * @throws RepositoryException
      * @see javax.jcr.version.VersionHistory#getVersionLabels()
      */
+    @SuppressWarnings("deprecation")
     public void testGetVersionLabels() throws RepositoryException {
 
         Set<String> testLabels = new HashSet<String>(Arrays.asList(vHistory.getVersionLabels()));
@@ -345,7 +351,7 @@ public class VersionLabelTest extends AbstractVersionTest {
 
         vHistory.addVersionLabel(v.getName(), versionLabel, false);
         testLabels.add(versionLabel);
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel2, false);
+        vHistory.addVersionLabel(version.getName(), versionLabel2, false);
         testLabels.add(versionLabel2);
 
         String[] labels = vHistory.getVersionLabels();
@@ -377,7 +383,7 @@ public class VersionLabelTest extends AbstractVersionTest {
 
         vHistory.addVersionLabel(v.getName(), versionLabel, false);
         testLabels.add(versionLabel);
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel2, false);
+        vHistory.addVersionLabel(version.getName(), versionLabel2, false);
         testLabels.add(versionLabel2);
 
         String[] labels = vHistory.getVersionLabels();
@@ -399,11 +405,12 @@ public class VersionLabelTest extends AbstractVersionTest {
      * @throws RepositoryException
      * @see VersionHistory#getVersionLabels(javax.jcr.version.Version)
      */
+    @SuppressWarnings("deprecation")
     public void testGetVersionLabelsForVersion() throws RepositoryException {
 
-        Set<String> testLabels = new HashSet<String>(Arrays.asList(vHistory.getVersionLabels(rootVersion)));
+        Set<String> testLabels = new HashSet<String>(Arrays.asList(vHistory.getVersionLabels(version)));
 
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel, false);
+        vHistory.addVersionLabel(version.getName(), versionLabel, false);
         testLabels.add(versionLabel);
 
         // add a version label to another version (not added to the testLabel set)
@@ -411,7 +418,7 @@ public class VersionLabelTest extends AbstractVersionTest {
         Version v = versionableNode.checkin();
         vHistory.addVersionLabel(v.getName(), versionLabel2, false);
 
-        String[] labels = vHistory.getVersionLabels(rootVersion);
+        String[] labels = vHistory.getVersionLabels(version);
         for (int i = 0; i < labels.length; i++) {
             String l = labels[i];
             if (!testLabels.contains(l)) {
@@ -432,9 +439,9 @@ public class VersionLabelTest extends AbstractVersionTest {
      */
     public void testGetVersionLabelsForVersionJcr2() throws RepositoryException {
 
-        Set<String> testLabels = new HashSet<String>(Arrays.asList(vHistory.getVersionLabels(rootVersion)));
+        Set<String> testLabels = new HashSet<String>(Arrays.asList(vHistory.getVersionLabels(version)));
 
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel, false);
+        vHistory.addVersionLabel(version.getName(), versionLabel, false);
         testLabels.add(versionLabel);
 
         // add a version label to another version (not added to the testLabel set)
@@ -444,7 +451,7 @@ public class VersionLabelTest extends AbstractVersionTest {
         Version v = versionManager.checkin(path);
         vHistory.addVersionLabel(v.getName(), versionLabel2, false);
 
-        String[] labels = vHistory.getVersionLabels(rootVersion);
+        String[] labels = vHistory.getVersionLabels(version);
         for (int i = 0; i < labels.length; i++) {
             String l = labels[i];
             if (!testLabels.contains(l)) {
@@ -462,6 +469,7 @@ public class VersionLabelTest extends AbstractVersionTest {
      * @throws javax.jcr.RepositoryException
      * @see javax.jcr.Node#restoreByLabel(String, boolean)
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreByLabelNonVersionableNode() throws RepositoryException {
         try {
             nonVersionableNode.restoreByLabel(versionLabel, true);
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/WorkspaceMoveVersionExceptionTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/WorkspaceMoveVersionExceptionTest.java
index d99501b..60f5cb4 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/WorkspaceMoveVersionExceptionTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/WorkspaceMoveVersionExceptionTest.java
@@ -43,8 +43,8 @@ public class WorkspaceMoveVersionExceptionTest extends AbstractVersionTest {
         // check the parent node in
         versionableNode.checkin();
         try {
-            // try to move the sub node this should throw an VersionException either instantly or upon save()
-            superuser.getWorkspace().move(movingNode.getPath(), nonVersionableNode.getPath());
+            // try to move the sub node this should throw an VersionException
+            superuser.getWorkspace().move(movingNode.getPath(), nonVersionableNode.getPath() + "/" + nodeName1);
             fail("Moving a node using Workspace.move() where parent node is " +
                     "versionable and checked in should throw a VersionException!");
         } catch (VersionException e) {
@@ -62,7 +62,7 @@ public class WorkspaceMoveVersionExceptionTest extends AbstractVersionTest {
         versionableNode.checkin();
 
         try {
-            // try to move the sub node this should throw an VersionException either instantly or upon save()
+            // try to move the sub node this should throw an VersionException
             superuser.getWorkspace().move(nonVersionableNode.getPath(), versionableNode.getPath() + "/" + nodeName1);
             fail("Moving a node using Workspace.move() where destination parent " +
                     "node is versionable and checked in should throw a VersionException!");
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/WorkspaceRestoreTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/WorkspaceRestoreTest.java
index f2b0fe6..63ac8f2 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/WorkspaceRestoreTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/WorkspaceRestoreTest.java
@@ -161,6 +161,7 @@ public class WorkspaceRestoreTest extends AbstractVersionTest {
      * Test if InvalidItemStateException is thrown if the session affected by
      * Workspace.restore(Version[], boolean) has pending changes.
      */
+    @SuppressWarnings("deprecation")
     public void testWorkspaceRestoreWithPendingChanges() throws RepositoryException {
         versionableNode.checkout();
         try {
@@ -203,6 +204,7 @@ public class WorkspaceRestoreTest extends AbstractVersionTest {
      * Test if VersionException is thrown if the specified version array does
      * not contain a version that has a corresponding node in this workspace.
      */
+    @SuppressWarnings("deprecation")
     public void testWorkspaceRestoreHasCorrespondingNode() throws RepositoryException {
         try {
             superuser.getWorkspace().restore(new Version[]{wChildVersion}, false);
@@ -234,6 +236,7 @@ public class WorkspaceRestoreTest extends AbstractVersionTest {
      * node in the workspace.</li>
      * </ul>
      */
+    @SuppressWarnings("deprecation")
     public void testWorkspaceRestoreWithParent() throws RepositoryException {
 
         try {
@@ -266,6 +269,7 @@ public class WorkspaceRestoreTest extends AbstractVersionTest {
     /**
      * Test if the removeExisting-flag removes an existing node in case of uuid conflict.
      */
+    @SuppressWarnings("deprecation")
     public void testWorkspaceRestoreWithRemoveExisting() throws NotExecutableException, RepositoryException {
         // create version for parentNode of childNode
         superuser.getWorkspace().clone(workspaceName, wVersionableChildNode.getPath(), wVersionableChildNode.getPath(), false);
@@ -309,6 +313,7 @@ public class WorkspaceRestoreTest extends AbstractVersionTest {
      * Tests if restoring the <code>Version</code> of an existing node throws an
      * <code>ItemExistsException</code> if removeExisting is set to FALSE.
      */
+    @SuppressWarnings("deprecation")
     public void testWorkspaceRestoreWithUUIDConflict() throws RepositoryException, NotExecutableException {
         try {
             // Verify that nodes used for the test are indeed versionable
@@ -359,6 +364,7 @@ public class WorkspaceRestoreTest extends AbstractVersionTest {
     /**
      * Test if workspace-restoring a node works on checked-in node.
      */
+    @SuppressWarnings("deprecation")
     public void testWorkspaceRestoreOnCheckedInNode() throws RepositoryException {
         if (versionableNode.isCheckedOut()) {
             versionableNode.checkin();
@@ -381,6 +387,7 @@ public class WorkspaceRestoreTest extends AbstractVersionTest {
     /**
      * Test if workspace-restoring a node works on checked-out node.
      */
+    @SuppressWarnings("deprecation")
     public void testWorkspaceRestoreOnCheckedOutNode() throws RepositoryException {
         if (!versionableNode.isCheckedOut()) {
             versionableNode.checkout();
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/simple/AbstractVersionTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/simple/AbstractVersionTest.java
index 883bb5e..d142aad 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/simple/AbstractVersionTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/simple/AbstractVersionTest.java
@@ -17,6 +17,7 @@
 package org.apache.jackrabbit.test.api.version.simple;
 
 import javax.jcr.Node;
+import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 import javax.jcr.nodetype.NoSuchNodeTypeException;
 import javax.jcr.nodetype.NodeType;
@@ -43,6 +44,8 @@ public class AbstractVersionTest extends AbstractJCRTest {
     protected void setUp() throws Exception {
         super.setUp();
 
+        super.checkSupportedOption(Repository.OPTION_SIMPLE_VERSIONING_SUPPORTED);
+
         NodeTypeManager ntMgr = superuser.getWorkspace().getNodeTypeManager();
 
         // assert that this repository support versioning
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/simple/CheckinTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/simple/CheckinTest.java
index d8d5c27..32440a5 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/simple/CheckinTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/simple/CheckinTest.java
@@ -51,6 +51,7 @@ public class CheckinTest extends AbstractVersionTest {
      *
      * @throws javax.jcr.RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testIsCheckedOut() throws RepositoryException {
         versionableNode.checkin();
         assertTrue("After calling Node.checkin() on a versionable node N, N.isCheckedOut() must return false", versionableNode.isCheckedOut() == false);
@@ -74,6 +75,7 @@ public class CheckinTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testMultipleCheckinHasNoEffect() throws RepositoryException {
 
         Version v = versionableNode.checkin();
@@ -112,6 +114,7 @@ public class CheckinTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testCheckinWithPendingChanges() throws RepositoryException {
         try {
             // modify node without calling save()
@@ -149,6 +152,7 @@ public class CheckinTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testIsNotCheckedOut() throws RepositoryException {
         versionableNode.checkin();
         boolean isCheckedOut = versionableNode.isCheckedOut();
@@ -175,6 +179,7 @@ public class CheckinTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testCheckinCreatesNewVersion() throws RepositoryException {
 
         long initialNumberOfVersions = getNumberOfVersions(versionableNode.getVersionHistory());
@@ -205,6 +210,7 @@ public class CheckinTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testCheckinNonVersionableNode() throws RepositoryException {
         try {
             nonVersionableNode.checkin();
diff --git a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/simple/RestoreTest.java b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/simple/RestoreTest.java
index 20dbc7d..19de362 100644
--- a/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/simple/RestoreTest.java
+++ b/jackrabbit-jcr-tests/src/main/java/org/apache/jackrabbit/test/api/version/simple/RestoreTest.java
@@ -105,6 +105,7 @@ public class RestoreTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreRootVersionFail() throws RepositoryException {
         try {
             versionableNode.restore(rootVersion, true);
@@ -133,6 +134,7 @@ public class RestoreTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreOnCheckedInNode() throws RepositoryException {
         versionableNode.checkin();
         versionableNode.restore(version, true);
@@ -189,6 +191,7 @@ public class RestoreTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreOnCheckedOutNode() throws RepositoryException {
         versionableNode.restore(version, true);
     }
@@ -240,6 +243,7 @@ public class RestoreTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreSetsIsCheckedOutToFalse() throws RepositoryException {
         versionableNode.restore(version, true);
         assertFalse("Restoring a node sets the jcr:isCheckedOut property to false", versionableNode.isCheckedOut());
@@ -296,6 +300,7 @@ public class RestoreTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreCorrectProperty() throws RepositoryException {
         versionableNode.restore(version, true);
         String value = versionableNode.getProperty(propertyName1).getString();
@@ -357,6 +362,7 @@ public class RestoreTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreWithPendingChanges() throws RepositoryException {
         // modify node without calling save()
         try {
@@ -443,6 +449,7 @@ public class RestoreTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreInvalidVersion() throws RepositoryException {
         Version vNode2 = versionableNode2.checkin();
         try {
@@ -476,6 +483,7 @@ public class RestoreTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreInvalidVersion2() throws RepositoryException {
         String invalidName;
         do {
@@ -529,6 +537,7 @@ public class RestoreTest extends AbstractVersionTest {
      * @throws RepositoryException
      * @see Node#restore(String, boolean)
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreNonVersionableNode() throws RepositoryException {
         try {
             nonVersionableNode.restore("foo", true);
@@ -576,6 +585,7 @@ public class RestoreTest extends AbstractVersionTest {
      * @throws RepositoryException
      * @see Node#restore(Version, boolean)
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreNonVersionableNode3() throws RepositoryException {
         try {
             nonVersionableNode.restore(version, true);
@@ -605,6 +615,7 @@ public class RestoreTest extends AbstractVersionTest {
      *
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreWithInvalidVersion() throws RepositoryException {
         Version invalidVersion = versionableNode2.checkin();
         try {
@@ -634,6 +645,7 @@ public class RestoreTest extends AbstractVersionTest {
      * Tests if restoring the <code>Version</code> of an existing node throws an
      * <code>ItemExistsException</code> if removeExisting is set to FALSE.
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreWithUUIDConflict() throws RepositoryException, NotExecutableException {
         try {
             Node naa = createVersionableNode(versionableNode, nodeName4, versionableNodeType);
@@ -755,6 +767,7 @@ public class RestoreTest extends AbstractVersionTest {
         }
     }
 
+    @SuppressWarnings("deprecation")
     public void testRestoreChild1() throws RepositoryException {
         versionableNode.addNode("child1");
         versionableNode.getSession().save();
@@ -859,6 +872,7 @@ public class RestoreTest extends AbstractVersionTest {
      * Test the restore of a versionable node using a label.
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreLabel() throws RepositoryException {
         // mark V1 with label test1
         versionableNode.getVersionHistory().addVersionLabel(version.getName(), "test", true);
@@ -887,6 +901,7 @@ public class RestoreTest extends AbstractVersionTest {
      * Test the restore of the OPV=Version child nodes.
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreName() throws RepositoryException,
             NotExecutableException {
         // V1.0 of versionableNode has no child
@@ -967,6 +982,7 @@ public class RestoreTest extends AbstractVersionTest {
      * Test the child ordering of restored nodes.
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreOrder() throws RepositoryException,
             NotExecutableException {
         // create a test-root that has orderable child nodes
@@ -1172,6 +1188,7 @@ public class RestoreTest extends AbstractVersionTest {
      * Test the child ordering of restored nodes.
      * @throws RepositoryException
      */
+    @SuppressWarnings("deprecation")
     public void testRestoreOrder2() throws RepositoryException,
             NotExecutableException {
         // create a test-root that has orderable child nodes
@@ -1377,6 +1394,7 @@ public class RestoreTest extends AbstractVersionTest {
      * Tests if restore on simple versioning creates a new version that is
      * in the correct linear order.
      */
+    @SuppressWarnings("deprecation")
     public void testLinearVersions() throws Exception {
         // first get all linear versions
         VersionIterator iter = versionableNode.getVersionHistory().getAllLinearVersions();
diff --git a/jackrabbit-jcr2dav/pom.xml b/jackrabbit-jcr2dav/pom.xml
index c648651..81898b5 100644
--- a/jackrabbit-jcr2dav/pom.xml
+++ b/jackrabbit-jcr2dav/pom.xml
@@ -26,12 +26,16 @@
   <parent>
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>jackrabbit-parent</artifactId>
-    <version>2.3.6</version>
+    <version>2.10.1</version>
     <relativePath>../jackrabbit-parent/pom.xml</relativePath>
   </parent>
   <artifactId>jackrabbit-jcr2dav</artifactId>
   <name>Jackrabbit JCR to WebDAV</name>
 
+  <properties>
+    <org.apache.jackrabbit.jcr2dav.RepositoryStubImpl.port>0</org.apache.jackrabbit.jcr2dav.RepositoryStubImpl.port>
+  </properties>
+
   <profiles>
     <profile>
       <id>integrationTesting</id>
@@ -48,6 +52,10 @@
                   <value>true</value>
                 </property>
                 <property>
+                  <name>org.apache.jackrabbit.jcr2dav.RepositoryStubImpl.port</name>
+                  <value>${org.apache.jackrabbit.jcr2dav.RepositoryStubImpl.port}</value>
+                </property>
+                <property>
                   <name>known.issues</name>
                   <value>
                       <!--
@@ -75,20 +83,13 @@
                       org.apache.jackrabbit.test.api.lock.LockManagerTest#testAddInvalidLockToken
                       org.apache.jackrabbit.test.api.lock.LockManagerTest#testAddLockTokenToAnotherSession
                       org.apache.jackrabbit.test.api.lock.LockManagerTest#testLockTransfer2
-                      org.apache.jackrabbit.jcr2spi.lock.OpenScopedLockTest#testLogoutHasNoEffect 
+                      org.apache.jackrabbit.jcr2spi.lock.OpenScopedLockTest#testLogoutHasNoEffect
+                      <!-- JCR-3207 -->
+                      org.apache.jackrabbit.test.api.observation.NodeReorderTest#testNodeReorderMove
                       <!-- JCR-2533 : missing impl of checkQueryStatement -->                      
                       org.apache.jackrabbit.test.api.query.CreateQueryTest#testUnknownQueryLanguage
-                      <!-- JCR-2543 : query offset -->
-                      org.apache.jackrabbit.test.api.query.SetOffsetTest#testSetOffset
                       <!-- JCR-2533 : missing impl of checkQueryStatement -->
                       org.apache.jackrabbit.test.api.query.qom.BindVariableValueTest
-                      <!-- JCR-2535 : Row.getPath() called with multiple selectors (server-side) -->
-                      org.apache.jackrabbit.test.api.query.qom.ChildNodeJoinConditionTest
-                      org.apache.jackrabbit.test.api.query.qom.ColumnTest#testMultiColumn
-                      org.apache.jackrabbit.test.api.query.qom.DescendantNodeJoinConditionTest
-                      org.apache.jackrabbit.test.api.query.qom.EquiJoinConditionTest
-                      org.apache.jackrabbit.test.api.query.qom.OrderingTest#testMultipleSelectors
-                      org.apache.jackrabbit.test.api.query.qom.SameNodeJoinConditionTest
                       <!-- JCR-2112 : simple versioning not implemented -->
                       org.apache.jackrabbit.test.api.version.simple
                       <!-- JCR-2104 : activities and configuration -->
@@ -103,6 +104,21 @@
                            No notification about changes to registered namespace(s) -->                        
                       org.apache.jackrabbit.jcr2spi.name.NamespaceRegistryTest#testReRegisteredNamespace
                       org.apache.jackrabbit.jcr2spi.name.NamespaceRegistryTest#testReRegisteredNamespaceVisibility
+                      org.apache.jackrabbit.test.api.query.qom.EquiJoinConditionTest#testRightOuterJoin1<!--JCR-3493, JCR-3498-->
+                      org.apache.jackrabbit.test.api.query.qom.EquiJoinConditionTest#testLeftOuterJoin2<!--JCR-3493, JCR-3498-->
+
+                    <!-- security related tests -->
+                      <!-- The following 5 tests don't make too much sense on a jcr-client that doesn't necessarily should
+                           know about the concrete implementation and thus might just collection all information and
+                           delegate any optimization to the server-side -->
+                      org.apache.jackrabbit.test.api.security.AccessControlListTest#testAddAggregatedPrivilegesSeparately <!-- JCR-3832 -->
+                      org.apache.jackrabbit.test.api.security.AccessControlListTest#testAddAccessControlEntryInvalidPrincipal
+                      org.apache.jackrabbit.test.api.security.AccessControlListTest#testAddAccessControlEntryInvalidPrivilege
+                      org.apache.jackrabbit.test.api.security.AccessControlListTest#testAddAccessControlEntryTwice
+                      org.apache.jackrabbit.test.api.security.AccessControlListTest#testAddAccessControlEntryAgain
+                      <!-- Tests to verify -->
+                      org.apache.jackrabbit.test.api.security.RSessionAccessControlPolicyTest#testGetApplicablePolicies
+                      org.apache.jackrabbit.test.api.security.RSessionAccessControlPolicyTest#testGetPolicy
                   </value>
                 </property>
                 <property>
@@ -133,37 +149,37 @@
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr2spi</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-spi2dav</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
 
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-tests</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
     <dependency>
        <groupId>org.apache.jackrabbit</groupId>
        <artifactId>jackrabbit-jcr2spi</artifactId>
-       <version>2.3.6</version>
+       <version>${project.version}</version>
        <classifier>tests</classifier>
        <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-core</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-server</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
     <dependency>
diff --git a/jackrabbit-jcr2dav/src/main/resources/accessControlProvider.properties b/jackrabbit-jcr2dav/src/main/resources/accessControlProvider.properties
new file mode 100644
index 0000000..d89cc0e
--- /dev/null
+++ b/jackrabbit-jcr2dav/src/main/resources/accessControlProvider.properties
@@ -0,0 +1,16 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+org.apache.jackrabbit.jcr2spi.AccessControlProvider.class=org.apache.jackrabbit.jcr2spi.security.authorization.jackrabbit.acl.AccessControlProviderImpl
diff --git a/jackrabbit-jcr2dav/src/test/java/org/apache/jackrabbit/jcr2dav/ConformanceTest.java b/jackrabbit-jcr2dav/src/test/java/org/apache/jackrabbit/jcr2dav/ConformanceTest.java
index 5f8df18..313075e 100644
--- a/jackrabbit-jcr2dav/src/test/java/org/apache/jackrabbit/jcr2dav/ConformanceTest.java
+++ b/jackrabbit-jcr2dav/src/test/java/org/apache/jackrabbit/jcr2dav/ConformanceTest.java
@@ -21,6 +21,7 @@ import junit.framework.TestCase;
 import junit.framework.TestResult;
 import junit.framework.TestSuite;
 
+import org.apache.jackrabbit.jcr2spi.security.Jcr2SpiSecurityTestSuite;
 import org.apache.jackrabbit.jcr2spi.Jcr2SpiTestSuite;
 import org.apache.jackrabbit.test.JCRTestSuite;
 
@@ -34,6 +35,7 @@ public class ConformanceTest extends TestCase {
         if (Boolean.getBoolean("jackrabbit.test.integration")) {
             suite.addTest(new JCRTestSuite());
             suite.addTest(new Jcr2SpiTestSuite());
+            suite.addTest(new Jcr2SpiSecurityTestSuite());
             suite.addTest(new StopRepository());
         }
         return suite;
diff --git a/jackrabbit-jcr2dav/src/test/java/org/apache/jackrabbit/jcr2dav/RepositoryStubImpl.java b/jackrabbit-jcr2dav/src/test/java/org/apache/jackrabbit/jcr2dav/RepositoryStubImpl.java
index 63cdc28..44f5246 100644
--- a/jackrabbit-jcr2dav/src/test/java/org/apache/jackrabbit/jcr2dav/RepositoryStubImpl.java
+++ b/jackrabbit-jcr2dav/src/test/java/org/apache/jackrabbit/jcr2dav/RepositoryStubImpl.java
@@ -16,13 +16,19 @@
  */
 package org.apache.jackrabbit.jcr2dav;
 
+import java.security.Principal;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Properties;
 
 import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
 
 import org.apache.jackrabbit.commons.JcrUtils;
 import org.apache.jackrabbit.core.JackrabbitRepositoryStub;
 import org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet;
+import org.apache.jackrabbit.test.NotExecutableException;
 import org.apache.jackrabbit.test.RepositoryStubException;
 import org.apache.jackrabbit.webdav.jcr.JCRWebdavServerServlet;
 import org.mortbay.jetty.Server;
@@ -32,6 +38,10 @@ import org.mortbay.jetty.servlet.ServletHolder;
 
 public class RepositoryStubImpl extends JackrabbitRepositoryStub {
 
+    private static final String PROP_ACCESSCONTROL_PROVIDER_CLASS = "org.apache.jackrabbit.jcr2spi.AccessControlProvider.class";
+
+    private static final String PROP_PROTECTED_ITEM_REMOVE_CLASS = "org.apache.jackrabbit.server.ProtectedItemRemoveHandler.class";
+
     private static Repository repository;
 
     private static SocketConnector connector;
@@ -40,8 +50,14 @@ public class RepositoryStubImpl extends JackrabbitRepositoryStub {
 
     private static Repository client;
 
+    private final String acProviderImplClass;
+
+    private final String protectedRemoveImplClass;
+
     public RepositoryStubImpl(Properties env) {
         super(env);
+        acProviderImplClass = env.getProperty(PROP_ACCESSCONTROL_PROVIDER_CLASS);
+        protectedRemoveImplClass = env.getProperty(PROP_PROTECTED_ITEM_REMOVE_CLASS);
     }
 
     @Override
@@ -53,25 +69,23 @@ public class RepositoryStubImpl extends JackrabbitRepositoryStub {
         if (connector == null) {
             connector = new SocketConnector();
             connector.setHost("localhost");
-            connector.setPort(0);
+            String pvalue = System.getProperty("org.apache.jackrabbit.jcr2dav.RepositoryStubImpl.port", "0");
+            int port = pvalue.equals("") ? 0 : Integer.parseInt(pvalue);
+            connector.setPort(port);
         }
 
         if (server == null) {
             server = new Server();
             server.addConnector(connector);
 
-            ServletHolder holder = new ServletHolder(
-                    new JcrRemotingServlet() {
-                        protected Repository getRepository() {
-                            return repository;
-                        }
-                    });
-            holder.setInitParameter(
-                    JCRWebdavServerServlet.INIT_PARAM_RESOURCE_PATH_PREFIX,
-                    "");
-            holder.setInitParameter(
-                    JCRWebdavServerServlet.INIT_PARAM_MISSING_AUTH_MAPPING,
-                    "");
+            ServletHolder holder = new ServletHolder(new JcrRemotingServlet() {
+                protected Repository getRepository() {
+                    return repository;
+                }
+            });
+            holder.setInitParameter(JCRWebdavServerServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, "");
+            holder.setInitParameter(JCRWebdavServerServlet.INIT_PARAM_MISSING_AUTH_MAPPING, "");
+            holder.setInitParameter(JcrRemotingServlet.INIT_PARAM_PROTECTED_HANDLERS_CONFIG, protectedRemoveImplClass);
 
             Context context = new Context(server, "/");
             context.addServlet(holder, "/*");
@@ -80,28 +94,53 @@ public class RepositoryStubImpl extends JackrabbitRepositoryStub {
             try {
                 server.start();
             } catch (Exception e) {
-                e.printStackTrace();
-                throw new RepositoryStubException(e.getMessage());
+                throw new RepositoryStubException(e);
             }
         }
 
         if (client == null) {
             try {
-                client = JcrUtils.getRepository(
-                        "http://localhost:" + connector.getLocalPort() + "/");
+                Map<String, String> parameters = new HashMap<String, String>();
+
+                String uri = "http://localhost:" + connector.getLocalPort() + "/";
+
+                parameters.put(JcrUtils.REPOSITORY_URI, uri);
+                parameters.put(PROP_ACCESSCONTROL_PROVIDER_CLASS, acProviderImplClass);
+
+                client = JcrUtils.getRepository(parameters);
             } catch (Exception e) {
-                e.printStackTrace();
-                throw new RepositoryStubException(e.getMessage());
+                throw new RepositoryStubException(e);
             }
         }
 
         return client;
     }
 
+    @Override
+    public Principal getKnownPrincipal(Session session) throws RepositoryException {
+        // TODO
+        return new Principal() {
+            @Override
+            public String getName() {
+                return "everyone";
+            }
+        };
+    }
+
+    @Override
+    public Principal getUnknownPrincipal(Session session) throws RepositoryException, NotExecutableException {
+        // TODO
+        return new Principal() {
+            @Override
+            public String getName() {
+                return "unknownPrincipal";
+            }
+        };
+    }
+
     public static void stopServer() throws Exception {
         if (server != null) {
             server.stop();
         }
     }
-
 }
diff --git a/jackrabbit-jcr2dav/src/test/resources/repository.xml b/jackrabbit-jcr2dav/src/test/resources/repository.xml
new file mode 100644
index 0000000..95f050e
--- /dev/null
+++ b/jackrabbit-jcr2dav/src/test/resources/repository.xml
@@ -0,0 +1,166 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 2.6//EN"
+                            "http://jackrabbit.apache.org/dtd/repository-2.6.dtd">
+<!-- Example Repository Configuration File -->
+<Repository>
+    <!--
+        virtual file system where the repository stores global state
+        (e.g. registered namespaces, custom node types, etc.)
+    -->
+    <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+        <param name="path" value="${rep.home}/repository"/>
+    </FileSystem>
+
+    <!--
+        data store configuration
+    -->
+    <DataStore class="org.apache.jackrabbit.core.data.FileDataStore"/>
+    <!--
+        sample database data store configuration
+        <DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
+            <param name="url" value="jdbc:h2:~/test"/>
+            <param name="user" value="sa"/>
+            <param name="password" value="sa"/>
+        </DataStore>
+    -->
+    
+    <!--
+        repository lock mechanism configuration
+    <RepositoryLockMechanism class="org.apache.jackrabbit.core.util.CooperativeFileLock"/>
+    -->
+
+    <!--
+        security configuration
+    -->
+    <Security appName="Jackrabbit">
+        <!--
+            security manager:
+            class: FQN of class implementing the JackrabbitSecurityManager interface
+        -->
+        <SecurityManager class="org.apache.jackrabbit.core.UserPerWorkspaceSecurityManager">
+            <!-- <param name="config" value="${rep.home}/security.xml"/> -->
+        </SecurityManager>
+
+        <!--
+            access manager:
+            class: FQN of class implementing the AccessManager interface
+        -->
+        <AccessManager class="org.apache.jackrabbit.core.security.DefaultAccessManager">
+            <!-- <param name="config" value="${rep.home}/access.xml"/> -->
+        </AccessManager>
+
+        <LoginModule class="org.apache.jackrabbit.core.security.authentication.DefaultLoginModule">
+           <!-- 
+              anonymous user name ('anonymous' is the default value)
+            -->
+           <param name="anonymousId" value="anonymous"/>
+           <!--
+              administrator user id (default value if param is missing is 'admin')
+            -->
+           <param name="adminId" value="admin"/>
+           <!--
+              optional parameter 'principalProvider'.
+              the value refers to the class name of the PrincipalProvider implementation.
+           -->
+           <!-- <param name="principalProvider" value="..."/> -->
+        </LoginModule>
+    </Security>
+
+    <!--
+        location of workspaces root directory and name of default workspace
+    -->
+    <Workspaces rootPath="${rep.home}/workspaces" defaultWorkspace="default" maxIdleTime="2"/>
+    <!--
+        workspace configuration template:
+        used to create the initial workspace if there's no workspace yet
+    -->
+    <Workspace name="${wsp.name}">
+        <!--
+            virtual file system of the workspace:
+            class: FQN of class implementing the FileSystem interface
+        -->
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${wsp.home}"/>
+        </FileSystem>
+        <!--
+            persistence manager of the workspace:
+            class: FQN of class implementing the PersistenceManager interface
+        -->
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+          <param name="url" value="jdbc:derby:${wsp.home}/db;create=true"/>
+          <param name="schemaObjectPrefix" value="${wsp.name}_"/>
+        </PersistenceManager>
+        <!--
+            Search index and the file system it uses.
+            class: FQN of class implementing the QueryHandler interface
+        -->
+        <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+            <param name="path" value="${wsp.home}/index"/>
+        </SearchIndex>
+
+        <!--
+            XML Import configuration of the workspace
+        -->
+        <Import>
+          <ProtectedNodeImporter class="org.apache.jackrabbit.core.xml.AccessControlImporter"/>
+          <ProtectedPropertyImporter class="org.apache.jackrabbit.core.security.user.UserImporter">
+             <param name="importBehavior" value="besteffort"/>
+          </ProtectedPropertyImporter>
+        </Import>
+    </Workspace>
+
+    <!--
+        Configures the versioning
+    -->
+    <Versioning rootPath="${rep.home}/version">
+        <!--
+            Configures the filesystem to use for versioning for the respective
+            persistence manager
+        -->
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${rep.home}/version" />
+        </FileSystem>
+
+        <!--
+            Configures the persistence manager to be used for persisting version state.
+            Please note that the current versioning implementation is based on
+            a 'normal' persistence manager, but this could change in future
+            implementations.
+        -->
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+          <param name="url" value="jdbc:derby:${rep.home}/version/db;create=true"/>
+          <param name="schemaObjectPrefix" value="version_"/>
+        </PersistenceManager>
+    </Versioning>
+
+    <!--
+        Search index for content that is shared repository wide
+        (/jcr:system tree, contains mainly versions)
+    -->
+    <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+        <param name="path" value="${rep.home}/repository/index"/>
+    </SearchIndex>
+    
+    <!--
+        Run with a cluster journal
+    -->
+    <Cluster id="node1">
+        <Journal class="org.apache.jackrabbit.core.journal.MemoryJournal"/>
+    </Cluster>
+</Repository>
diff --git a/jackrabbit-jcr2dav/src/test/resources/repositoryStubImpl.properties b/jackrabbit-jcr2dav/src/test/resources/repositoryStubImpl.properties
index 40c9bdf..590bfcc 100644
--- a/jackrabbit-jcr2dav/src/test/resources/repositoryStubImpl.properties
+++ b/jackrabbit-jcr2dav/src/test/resources/repositoryStubImpl.properties
@@ -15,3 +15,23 @@
 
 # Stub implementation class
 javax.jcr.tck.repository_stub_impl=org.apache.jackrabbit.jcr2dav.RepositoryStubImpl
+
+# Access control provider implementation class
+org.apache.jackrabbit.jcr2spi.AccessControlProvider.class=org.apache.jackrabbit.jcr2spi.security.authorization.jackrabbit.acl.AccessControlProviderImpl
+
+# ProtectedItemRemoveHandler implementation class
+org.apache.jackrabbit.server.ProtectedItemRemoveHandler.class=org.apache.jackrabbit.server.remoting.davex.AclRemoveHandler
+
+# the repository home
+org.apache.jackrabbit.repository.home=target/repository
+
+# the repository configuration
+org.apache.jackrabbit.repository.config=target/test-classes/repository.xml
+
+# credential configuration
+javax.jcr.tck.superuser.name=admin
+javax.jcr.tck.superuser.pwd=admin
+javax.jcr.tck.readwrite.name=user
+javax.jcr.tck.readwrite.pwd=user
+javax.jcr.tck.readonly.name=anonymous
+javax.jcr.tck.readonly.pwd=
diff --git a/jackrabbit-jcr2spi/pom.xml b/jackrabbit-jcr2spi/pom.xml
index dc05387..4b3d952 100644
--- a/jackrabbit-jcr2spi/pom.xml
+++ b/jackrabbit-jcr2spi/pom.xml
@@ -26,7 +26,7 @@
   <parent>
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>jackrabbit-parent</artifactId>
-    <version>2.3.6</version>
+    <version>2.10.1</version>
     <relativePath>../jackrabbit-parent/pom.xml</relativePath>
   </parent>
   <artifactId>jackrabbit-jcr2spi</artifactId>
@@ -82,24 +82,23 @@
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
-      <artifactId>jackrabbit-spi</artifactId>
-      <version>2.3.6</version>
+      <artifactId>jackrabbit-api</artifactId>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
-      <artifactId>jackrabbit-spi-commons</artifactId>
-      <version>2.3.6</version>
+      <artifactId>jackrabbit-spi</artifactId>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
-      <artifactId>jackrabbit-jcr-commons</artifactId>
-      <version>2.3.6</version>
+      <artifactId>jackrabbit-spi-commons</artifactId>
+      <version>${project.version}</version>
     </dependency>
-    <!-- tmp dependency to jackrabbit-api until jsr 283 is released -->
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
-      <artifactId>jackrabbit-api</artifactId>
-      <version>2.3.6</version>
+      <artifactId>jackrabbit-jcr-commons</artifactId>
+      <version>${project.version}</version>
     </dependency>
     <!-- end of tmp dependency -->
     <dependency>
@@ -123,7 +122,7 @@
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-tests</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
     <dependency>
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ItemLifeCycleListener.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ItemLifeCycleListener.java
index 7128f51..bd6d7b2 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ItemLifeCycleListener.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ItemLifeCycleListener.java
@@ -45,7 +45,7 @@ public interface ItemLifeCycleListener {
     /**
      * Called when an <code>ItemImpl</code> instance has been destroyed
      * (i.e. it has been permanently rendered 'invalid').
-     * <p/>
+     * <p>
      * Note that most <code>{@link javax.jcr.Item}</code>,
      * <code>{@link javax.jcr.Node}</code> and <code>{@link javax.jcr.Property}</code>
      * methods will throw an <code>InvalidItemStateException</code> when called
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ItemManager.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ItemManager.java
index bc336c9..82a35a7 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ItemManager.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ItemManager.java
@@ -33,7 +33,7 @@ import javax.jcr.Property;
  * There's one <code>ItemManager</code> instance per <code>Session</code>
  * instance. It is the factory for <code>Node</code> and <code>Property</code>
  * instances.
- * <p/>
+ * <p>
  * The <code>ItemManager</code>'s responsibilities are:
  * <ul>
  * <li>providing access to <code>Item</code> instances by <code>ItemState</code>
@@ -47,7 +47,7 @@ import javax.jcr.Property;
  * <code>Session</code> instance.
  * <li>maintaining a cache of the item instances it created.
  * </ul>
- * <p/>
+ * <p>
  * If the parent <code>Session</code> is an <code>XASession</code>, there is
  * one <code>ItemManager</code> instance per started global transaction.
  */
@@ -63,24 +63,27 @@ public interface ItemManager {
      *
      * @param path path to the node to be checked
      * @return true if the specified item exists
+     * @throws RepositoryException
      */
-    public boolean nodeExists(Path path);
+    public boolean nodeExists(Path path) throws RepositoryException;
 
     /**
      * Checks if the property with the given path exists.
      *
      * @param path path to the property to be checked
      * @return true if the specified item exists
+     * @throws RepositoryException
      */
-    public boolean propertyExists(Path path);
+    public boolean propertyExists(Path path) throws RepositoryException;
 
     /**
      * Checks if the item for given HierarchyEntry exists.
      *
      * @param hierarchyEntry
      * @return true if the specified item exists
+      @throws RepositoryException
      */
-    public boolean itemExists(HierarchyEntry hierarchyEntry);
+    public boolean itemExists(HierarchyEntry hierarchyEntry) throws RepositoryException;
 
     /**
      *
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ItemManagerImpl.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ItemManagerImpl.java
index 6f4f348..4a72582 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ItemManagerImpl.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ItemManagerImpl.java
@@ -102,7 +102,7 @@ public class ItemManagerImpl implements ItemManager, ItemStateCreationListener {
     /**
      * @see ItemManager#nodeExists(Path)
      */
-    public boolean nodeExists(Path path) {
+    public boolean nodeExists(Path path) throws RepositoryException {
         try {
             // session-sanity & permissions are checked upon itemExists(ItemState)
             NodeState nodeState = hierMgr.getNodeState(path);
@@ -111,15 +111,13 @@ public class ItemManagerImpl implements ItemManager, ItemStateCreationListener {
             return false;
         } catch (ItemNotFoundException infe) {
             return false;
-        } catch (RepositoryException re) {
-            return false;
         }
     }
 
     /**
      * @see ItemManager#propertyExists(Path)
      */
-    public boolean propertyExists(Path path) {
+    public boolean propertyExists(Path path) throws RepositoryException {
         try {
             // session-sanity & permissions are checked upon itemExists(ItemState)
             PropertyState propState = hierMgr.getPropertyState(path);
@@ -128,23 +126,19 @@ public class ItemManagerImpl implements ItemManager, ItemStateCreationListener {
             return false;
         } catch (ItemNotFoundException infe) {
             return false;
-        } catch (RepositoryException re) {
-            return false;
         }
     }
 
     /**
      * @see ItemManager#itemExists(HierarchyEntry)
      */
-    public boolean itemExists(HierarchyEntry hierarchyEntry) {
+    public boolean itemExists(HierarchyEntry hierarchyEntry) throws RepositoryException {
         try {
             // session-sanity & permissions are checked upon itemExists(ItemState)
             ItemState state = hierarchyEntry.getItemState();
             return itemExists(state);
         } catch (ItemNotFoundException e) {
             return false;
-        } catch (RepositoryException e) {
-            return false;
         }
     }
 
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/Jcr2spiRepositoryFactory.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/Jcr2spiRepositoryFactory.java
index fb3bf90..3f74a85 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/Jcr2spiRepositoryFactory.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/Jcr2spiRepositoryFactory.java
@@ -152,12 +152,10 @@ public class Jcr2spiRepositoryFactory implements RepositoryFactory {
                 log.warn("Ignoring {} since {} was specified", PARAM_REPOSITORY_SERVICE_FACTORY,
                         PARAM_REPOSITORY_CONFIG);
             }
-        }
-        else {
+        } else {
             if (serviceFactory == null) {
                 return null;
-            }
-            else {
+            } else {
                 config = new RepositoryConfigImpl(serviceFactory, parameters);
             }
         }
@@ -181,17 +179,15 @@ public class Jcr2spiRepositoryFactory implements RepositoryFactory {
         if (serviceFactoryParam instanceof RepositoryServiceFactory) {
             log.debug("Found RepositoryServiceFactory {}", serviceFactoryParam);
             return (RepositoryServiceFactory) serviceFactoryParam;
-        }
-        else if (serviceFactoryParam instanceof String) {
-            String serviceFactoryName = (String)serviceFactoryParam;
+        } else if (serviceFactoryParam instanceof String) {
+            String serviceFactoryName = (String) serviceFactoryParam;
             log.debug("Found RepositoryServiceFactory class name {}", serviceFactoryName);
             try {
                 Class<?> serviceFactoryClass;
                 try {
                     serviceFactoryClass = Class.forName(serviceFactoryName, true,
-                                Thread.currentThread().getContextClassLoader());
-                }
-                catch (ClassNotFoundException e) {
+                            Thread.currentThread().getContextClassLoader());
+                } catch (ClassNotFoundException e) {
                     // Backup for OSGi
                     serviceFactoryClass = Class.forName(serviceFactoryName);
                 }
@@ -201,20 +197,17 @@ public class Jcr2spiRepositoryFactory implements RepositoryFactory {
                 if (serviceFactory instanceof RepositoryServiceFactory) {
                     log.debug("Found RepositoryServiceFactory {}", serviceFactory);
                     return (RepositoryServiceFactory) serviceFactory;
-                }
-                else {
+                } else {
                     String msg = "Error acquiring RepositoryServiceFactory " + serviceFactoryParam;
                     log.error(msg);
                     throw new RepositoryException(msg);
                 }
-            }
-            catch (Exception e) {
+            } catch (Exception e) {
                 String msg = "Error acquiring RepositoryServiceFactory";
                 log.error(msg, e);
                 throw new RepositoryException(msg, e);
             }
-        }
-        else {
+        } else {
             String msg = "Error acquiring RepositoryServiceFactory from " + serviceFactoryParam;
             log.error(msg);
             throw new RepositoryException(msg);
@@ -252,6 +245,20 @@ public class Jcr2spiRepositoryFactory implements RepositoryFactory {
             return pollTimeOut;
         }
 
+        @Override
+        public <T> T getConfiguration(String name, T defaultValue) {
+            if (parameters.containsKey(name)) {
+                Object value = parameters.get(name);
+                Class clazz = (defaultValue == null)
+                        ? value.getClass()
+                        : defaultValue.getClass();
+                if (clazz.isAssignableFrom(value.getClass())) {
+                    return (T) value;
+                }
+            }
+            return defaultValue;
+        }
+
         public RepositoryService getRepositoryService() throws RepositoryException {
             if (repositoryService == null) {
                 repositoryService = serviceFactory.createRepositoryService(parameters);
@@ -268,27 +275,22 @@ public class Jcr2spiRepositoryFactory implements RepositoryFactory {
             if (paramCacheBehaviour == null) {
                 log.debug("{} not set, defaulting to {}", PARAM_CACHE_BEHAVIOR, DEFAULT_CACHE_BEHAVIOR);
                 return DEFAULT_CACHE_BEHAVIOR;
-            }
-            else if (paramCacheBehaviour instanceof CacheBehaviour) {
+            } else if (paramCacheBehaviour instanceof CacheBehaviour) {
                 log.debug("Setting CacheBehaviour to {}", paramCacheBehaviour);
                 return (CacheBehaviour) paramCacheBehaviour;
-            }
-            else if (paramCacheBehaviour instanceof String) {
+            } else if (paramCacheBehaviour instanceof String) {
                 String cacheBehaviour = (String) paramCacheBehaviour;
                 if ("invalidate".equals(cacheBehaviour)) {
                     log.debug("Setting CacheBehaviour to {}", CacheBehaviour.INVALIDATE);
                     return CacheBehaviour.INVALIDATE;
-                }
-                else if ("observation".equals(cacheBehaviour)) {
+                } else if ("observation".equals(cacheBehaviour)) {
                     log.debug("Setting CacheBehaviour to {}", CacheBehaviour.OBSERVATION);
                     return CacheBehaviour.OBSERVATION;
-                }
-                else {
-                    log.error("Invalid valid for CacheBehaviour: {}", PARAM_CACHE_BEHAVIOR, cacheBehaviour);
+                } else {
+                    log.error("Invalid valid for CacheBehaviour: {} {}", PARAM_CACHE_BEHAVIOR, cacheBehaviour);
                     throw new RepositoryException("Invalid value for CacheBehaviour: " + cacheBehaviour);
                 }
-            }
-            else {
+            } else {
                 String msg = "Invalid value for CacheBehaviour: " + paramCacheBehaviour;
                 log.error(msg);
                 throw new RepositoryException(msg);
@@ -302,23 +304,19 @@ public class Jcr2spiRepositoryFactory implements RepositoryFactory {
             if (paramItemCacheSize == null) {
                 log.debug("{} not set, defaulting to {}", PARAM_ITEM_CACHE_SIZE, DEFAULT_ITEM_CACHE_SIZE);
                 return DEFAULT_ITEM_CACHE_SIZE;
-            }
-            else if (paramItemCacheSize instanceof Integer) {
+            } else if (paramItemCacheSize instanceof Integer) {
                 log.debug("Setting ItemCacheSize to {}", paramItemCacheSize);
                 return (Integer) paramItemCacheSize;
-            }
-            else if (paramItemCacheSize instanceof String) {
+            } else if (paramItemCacheSize instanceof String) {
                 try {
                     log.debug("Setting ItemCacheSize to {}", paramItemCacheSize);
                     return Integer.parseInt((String) paramItemCacheSize);
-                }
-                catch (NumberFormatException e) {
+                } catch (NumberFormatException e) {
                     String msg = "Invalid value for ItemCacheSize: " + paramItemCacheSize;
                     log.error(msg);
                     throw new RepositoryException(msg, e);
                 }
-            }
-            else {
+            } else {
                 String msg = "Invalid value for ItemCacheSize: " + paramItemCacheSize;
                 log.error(msg);
                 throw new RepositoryException(msg);
@@ -332,23 +330,19 @@ public class Jcr2spiRepositoryFactory implements RepositoryFactory {
             if (paramPollTimeOut == null) {
                 log.debug("{} not set, defaulting to {}", PARAM_POLL_TIME_OUT, DEFAULT_POLL_TIME_OUT);
                 return DEFAULT_POLL_TIME_OUT;
-            }
-            else if (paramPollTimeOut instanceof Integer) {
+            } else if (paramPollTimeOut instanceof Integer) {
                 log.debug("Setting PollTimeout to {}", paramPollTimeOut);
                 return (Integer) paramPollTimeOut;
-            }
-            else if (paramPollTimeOut instanceof String) {
+            } else if (paramPollTimeOut instanceof String) {
                 try {
                     log.debug("Setting PollTimeout to {}", paramPollTimeOut);
                     return Integer.parseInt((String) paramPollTimeOut);
-                }
-                catch (NumberFormatException e) {
+                } catch (NumberFormatException e) {
                     String msg = "Invalid value for PollTimeout: " + paramPollTimeOut;
                     log.error(msg);
                     throw new RepositoryException(msg, e);
                 }
-            }
-            else {
+            } else {
                 String msg = "Invalid value for PollTimeout: " + paramPollTimeOut;
                 log.error(msg);
                 throw new RepositoryException(msg);
@@ -368,8 +362,7 @@ public class Jcr2spiRepositoryFactory implements RepositoryFactory {
             Object lwProvider = parameters.get(PARAM_LOG_WRITER_PROVIDER);
             if (lwProvider instanceof LogWriterProvider) {
                 service = SpiLoggerFactory.create(config.getRepositoryService(), (LogWriterProvider) lwProvider);
-            }
-            else {
+            } else {
                 service = SpiLoggerFactory.create(config.getRepositoryService());
             }
         }
@@ -396,6 +389,11 @@ public class Jcr2spiRepositoryFactory implements RepositoryFactory {
             return config.getPollTimeout();
         }
 
+        @Override
+        public <T> T getConfiguration(String name, T defaultValue) {
+            return config.getConfiguration(name, defaultValue);
+        }
+
         public RepositoryService getRepositoryService() throws RepositoryException {
             return service;
         }
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/LazyItemIterator.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/LazyItemIterator.java
index 74abc22..a0e9187 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/LazyItemIterator.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/LazyItemIterator.java
@@ -43,7 +43,7 @@ import org.slf4j.LoggerFactory;
 /**
  * <code>LazyItemIterator</code> is an id-based iterator that instantiates
  * the <code>Item</code>s only when they are requested.
- * <p/>
+ * <p>
  * <strong>Important:</strong> <code>Item</code>s that appear to be nonexistent
  * for some reason (e.g. because of insufficient access rights or because they
  * have been removed since the iterator has been retrieved) are silently
@@ -219,9 +219,17 @@ public class LazyItemIterator implements NodeIterator, PropertyIterator, Version
             pos++;
             HierarchyEntry entry = iter.next();
             // check if item exists but don't build Item instance.
-            while (!itemMgr.itemExists(entry)) {
-                log.debug("Ignoring nonexistent item " + entry);
-                entry = iter.next();
+            boolean itemExists = false;
+            while(!itemExists){
+                try{
+                    itemExists = itemMgr.itemExists(entry);
+                }catch(RepositoryException e){
+                    log.warn("Failed to check that item {} exists",entry,e);
+                }
+                if(!itemExists){
+                    log.debug("Ignoring nonexistent item {}", entry);
+                    entry = iter.next();
+                }
             }
         }
         // fetch final item (the one to be returned on next())
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ManagerProvider.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ManagerProvider.java
index e857188..c81480e 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ManagerProvider.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/ManagerProvider.java
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.jcr2spi;
 
+import org.apache.jackrabbit.jcr2spi.security.authorization.AccessControlProvider;
 import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
 import org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyManager;
 import org.apache.jackrabbit.jcr2spi.security.AccessManager;
@@ -84,4 +85,6 @@ public interface ManagerProvider {
     public ValueFactory getJcrValueFactory() throws RepositoryException;
 
     public QValueFactory getQValueFactory() throws RepositoryException;
+
+    public AccessControlProvider getAccessControlProvider() throws RepositoryException;
 }
\ No newline at end of file
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java
index beeb2b4..a71ff14 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/NodeImpl.java
@@ -1733,7 +1733,7 @@ public class NodeImpl extends ItemImpl implements Node {
     /**
      * Returns the <code>NodeEntry</code> at <code>relPath</code> or
      * <code>null</code> if no node exists at <code>relPath</code>.
-     * <p/>
+     * <p>
      * Note that access rights are not checked.
      *
      * @param relPath relative path of a (possible) node.
@@ -1775,7 +1775,7 @@ public class NodeImpl extends ItemImpl implements Node {
     /**
      * Returns the id of the property at <code>relPath</code> or <code>null</code>
      * if no property exists at <code>relPath</code>.
-     * <p/>
+     * <p>
      * Note that access rights are not checked.
      *
      * @param relPath relative path of a (possible) property
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/SessionImpl.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/SessionImpl.java
index 3bcbef7..7f278d9 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/SessionImpl.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/SessionImpl.java
@@ -51,7 +51,6 @@ import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
 
 import org.apache.commons.collections.map.ReferenceMap;
-import org.apache.commons.io.IOUtils;
 import org.apache.jackrabbit.commons.AbstractSession;
 import org.apache.jackrabbit.jcr2spi.config.CacheBehaviour;
 import org.apache.jackrabbit.jcr2spi.config.RepositoryConfig;
@@ -67,6 +66,7 @@ import org.apache.jackrabbit.jcr2spi.nodetype.NodeTypeManagerImpl;
 import org.apache.jackrabbit.jcr2spi.operation.Move;
 import org.apache.jackrabbit.jcr2spi.operation.Operation;
 import org.apache.jackrabbit.jcr2spi.security.AccessManager;
+import org.apache.jackrabbit.jcr2spi.security.authorization.AccessControlProvider;
 import org.apache.jackrabbit.jcr2spi.state.ItemStateFactory;
 import org.apache.jackrabbit.jcr2spi.state.ItemStateValidator;
 import org.apache.jackrabbit.jcr2spi.state.NodeState;
@@ -410,8 +410,7 @@ public class SessionImpl extends AbstractSession
         } catch (ParserConfigurationException e) {
             throw new RepositoryException("SAX parser configuration error", e);
         } finally {
-            // JCR-2903
-            IOUtils.closeQuietly(in);
+            in.close(); // JCR-2903
         }
     }
 
@@ -499,8 +498,7 @@ public class SessionImpl extends AbstractSession
     public AccessControlManager getAccessControlManager() throws RepositoryException {
         checkSupportedOption(Repository.OPTION_ACCESS_CONTROL_SUPPORTED);
 
-        // TODO: implementation missing
-        throw new UnsupportedRepositoryOperationException("JCR-1104");
+        return getAccessControlProvider().createAccessControlManager(sessionInfo, itemStateManager, itemManager, getItemDefinitionProvider(), getHierarchyManager(), npResolver);
     }
 
     /**
@@ -800,6 +798,13 @@ public class SessionImpl extends AbstractSession
     }
 
     /**
+     * @see ManagerProvider#getAccessControlProvider()
+     */
+    public AccessControlProvider getAccessControlProvider() throws RepositoryException {
+        return workspace.getAccessControlProvider();
+    }
+
+    /**
      * @see ManagerProvider#getJcrValueFactory()
      */
     public ValueFactory getJcrValueFactory() throws RepositoryException {
@@ -847,10 +852,6 @@ public class SessionImpl extends AbstractSession
         return config.getCacheBehaviour();
     }
 
-    int getPollTimeout() {
-        return config.getPollTimeout();
-    }
-
     //--------------------------------------------------------------------------
     SessionImpl switchWorkspace(String workspaceName) throws AccessDeniedException,
         NoSuchWorkspaceException, RepositoryException {
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceImpl.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceImpl.java
index 7116ab4..59ce90d 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceImpl.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceImpl.java
@@ -16,8 +16,6 @@
  */
 package org.apache.jackrabbit.jcr2spi;
 
-import org.apache.commons.io.IOUtils;
-import org.apache.jackrabbit.jcr2spi.config.CacheBehaviour;
 import org.apache.jackrabbit.jcr2spi.config.RepositoryConfig;
 import org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyManager;
 import org.apache.jackrabbit.jcr2spi.lock.LockManagerImpl;
@@ -34,6 +32,7 @@ import org.apache.jackrabbit.jcr2spi.operation.Operation;
 import org.apache.jackrabbit.jcr2spi.operation.WorkspaceImport;
 import org.apache.jackrabbit.jcr2spi.query.QueryManagerImpl;
 import org.apache.jackrabbit.jcr2spi.security.AccessManager;
+import org.apache.jackrabbit.jcr2spi.security.authorization.AccessControlProvider;
 import org.apache.jackrabbit.jcr2spi.state.ItemStateFactory;
 import org.apache.jackrabbit.jcr2spi.state.ItemStateValidator;
 import org.apache.jackrabbit.jcr2spi.state.NodeState;
@@ -46,7 +45,6 @@ import org.apache.jackrabbit.spi.NameFactory;
 import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.PathFactory;
 import org.apache.jackrabbit.spi.QValueFactory;
-import org.apache.jackrabbit.spi.RepositoryService;
 import org.apache.jackrabbit.spi.SessionInfo;
 import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
 import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
@@ -113,11 +111,7 @@ public class WorkspaceImpl implements Workspace, ManagerProvider {
     public WorkspaceImpl(String name, SessionImpl session, RepositoryConfig config, SessionInfo sessionInfo) throws RepositoryException {
         this.name = name;
         this.session = session;
-        wspManager = createManager(
-                config.getRepositoryService(),
-                sessionInfo,
-                session.getCacheBehaviour(),
-                session.getPollTimeout());
+        wspManager = createManager(config, sessionInfo);
     }
 
     //----------------------------------------------------------< Workspace >---
@@ -344,8 +338,7 @@ public class WorkspaceImpl implements Workspace, ManagerProvider {
             // run the import
             wspManager.execute(WorkspaceImport.create(parentState, in, uuidBehavior));
         } finally {
-            // JCR-2903
-            IOUtils.closeQuietly(in);
+            in. close(); // JCR-2903
         }
     }
 
@@ -502,6 +495,13 @@ public class WorkspaceImpl implements Workspace, ManagerProvider {
         return session.getQValueFactory();
     }
 
+    /**
+     * @see ManagerProvider#getAccessControlProvider() ()
+     */
+    public AccessControlProvider getAccessControlProvider() throws RepositoryException {
+        return wspManager.getAccessControlProvider();
+    }
+
     //------------------------------------< implementation specific methods >---
     void dispose() {
         // NOTE: wspManager has already been disposed upon SessionItemStateManager.dispose()
@@ -550,19 +550,14 @@ public class WorkspaceImpl implements Workspace, ManagerProvider {
     /**
      * Create the workspace state manager. May be overridden by subclasses.
      *
-     * @param service the RepositoryService
+     * @param config the RepositoryConfiguration
      * @param sessionInfo the SessionInfo used to create this instance.
-     * @param cacheBehaviour the desired cache behavior
-     * @param pollTimeout the desired pollTimeout.
-     * @return state manager
+     * @return workspace manager
      * @throws javax.jcr.RepositoryException If an error occurs
      */
-    protected WorkspaceManager createManager(RepositoryService service,
-                                             SessionInfo sessionInfo,
-                                             CacheBehaviour cacheBehaviour,
-                                             int pollTimeout) throws RepositoryException {
-        return new WorkspaceManager(service, sessionInfo, cacheBehaviour,
-                pollTimeout, session.isSupportedOption(Repository.OPTION_OBSERVATION_SUPPORTED));
+    protected WorkspaceManager createManager(RepositoryConfig config,
+                                             SessionInfo sessionInfo) throws RepositoryException {
+        return new WorkspaceManager(config, sessionInfo, session.isSupportedOption(Repository.OPTION_OBSERVATION_SUPPORTED));
     }
 
     /**
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java
index 41127f6..8a56072 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/WorkspaceManager.java
@@ -43,9 +43,12 @@ import javax.jcr.query.InvalidQueryException;
 import javax.jcr.version.VersionException;
 
 import org.apache.jackrabbit.jcr2spi.config.CacheBehaviour;
+import org.apache.jackrabbit.jcr2spi.config.RepositoryConfig;
 import org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyEventListener;
 import org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyManager;
 import org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyManagerImpl;
+import org.apache.jackrabbit.jcr2spi.hierarchy.NodeEntry;
+import org.apache.jackrabbit.jcr2spi.hierarchy.PropertyEntry;
 import org.apache.jackrabbit.jcr2spi.nodetype.EffectiveNodeTypeProvider;
 import org.apache.jackrabbit.jcr2spi.nodetype.ItemDefinitionProvider;
 import org.apache.jackrabbit.jcr2spi.nodetype.ItemDefinitionProviderImpl;
@@ -78,15 +81,19 @@ import org.apache.jackrabbit.jcr2spi.operation.ReorderNodes;
 import org.apache.jackrabbit.jcr2spi.operation.ResolveMergeConflict;
 import org.apache.jackrabbit.jcr2spi.operation.Restore;
 import org.apache.jackrabbit.jcr2spi.operation.SetMixin;
+import org.apache.jackrabbit.jcr2spi.operation.SetTree;
 import org.apache.jackrabbit.jcr2spi.operation.SetPrimaryType;
 import org.apache.jackrabbit.jcr2spi.operation.SetPropertyValue;
 import org.apache.jackrabbit.jcr2spi.operation.Update;
 import org.apache.jackrabbit.jcr2spi.operation.WorkspaceImport;
 import org.apache.jackrabbit.jcr2spi.security.AccessManager;
+import org.apache.jackrabbit.jcr2spi.security.authorization.AccessControlProvider;
+import org.apache.jackrabbit.jcr2spi.security.authorization.AccessControlProviderStub;
 import org.apache.jackrabbit.jcr2spi.state.ChangeLog;
 import org.apache.jackrabbit.jcr2spi.state.ItemState;
 import org.apache.jackrabbit.jcr2spi.state.ItemStateFactory;
 import org.apache.jackrabbit.jcr2spi.state.NodeState;
+import org.apache.jackrabbit.jcr2spi.state.PropertyState;
 import org.apache.jackrabbit.jcr2spi.state.Status;
 import org.apache.jackrabbit.jcr2spi.state.TransientISFactory;
 import org.apache.jackrabbit.jcr2spi.state.TransientItemStateFactory;
@@ -112,6 +119,8 @@ import org.apache.jackrabbit.spi.QueryInfo;
 import org.apache.jackrabbit.spi.RepositoryService;
 import org.apache.jackrabbit.spi.SessionInfo;
 import org.apache.jackrabbit.spi.Subscription;
+import org.apache.jackrabbit.spi.Tree;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
 import org.apache.jackrabbit.spi.commons.nodetype.NodeTypeStorage;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -124,6 +133,7 @@ public class WorkspaceManager
 
     private static Logger log = LoggerFactory.getLogger(WorkspaceManager.class);
 
+    private final RepositoryConfig config;
     private final RepositoryService service;
     private final SessionInfo sessionInfo;
     private final NameFactory nameFactory;
@@ -132,15 +142,15 @@ public class WorkspaceManager
     private final ItemStateFactory isf;
     private final HierarchyManager hierarchyManager;
 
-    private final CacheBehaviour cacheBehaviour;
     private final boolean observationSupported;
-    private final int pollTimeout;
 
     private final IdFactory idFactory;
     private final NamespaceRegistryImpl nsRegistry;
     private final NodeTypeRegistryImpl ntRegistry;
     private final ItemDefinitionProvider definitionProvider;
 
+    private AccessControlProvider acProvider;
+
     /**
      * Semaphore to synchronize the feed thread with client
      * threads that call {@link #execute(Operation)} or {@link
@@ -177,16 +187,13 @@ public class WorkspaceManager
      */
     private ItemInfoCache cache;
 
-    public WorkspaceManager(RepositoryService service, SessionInfo sessionInfo,
-                            CacheBehaviour cacheBehaviour, int pollTimeout,
-                            boolean observationSupported)
+    public WorkspaceManager(RepositoryConfig config, SessionInfo sessionInfo, boolean observationSupported)
         throws RepositoryException {
 
-        this.service = service;
+        this.config = config;
+        this.service = config.getRepositoryService();
         this.sessionInfo = sessionInfo;
-        this.cacheBehaviour = cacheBehaviour;
         this.observationSupported = observationSupported;
-        this.pollTimeout = pollTimeout;
 
         this.nameFactory = service.getNameFactory();
         this.pathFactory = service.getPathFactory();
@@ -206,6 +213,7 @@ public class WorkspaceManager
         // installed. Note: this listener has to be the first one called in order
         // for the hierarchy to be consistent with the event (See JCR-2293).
         InternalEventListener listener = createHierarchyListener(hierarchyManager);
+        CacheBehaviour cacheBehaviour = config.getCacheBehaviour();
         if (cacheBehaviour == CacheBehaviour.OBSERVATION) {
             addEventListener(listener);
         } else {
@@ -253,6 +261,18 @@ public class WorkspaceManager
         return isf;
     }
 
+    /**
+     * Locates and instantiates an AccessControlProvider implementation.
+     * @return      an access control manager provider.  
+     * @throws      RepositoryException
+     */
+    public AccessControlProvider getAccessControlProvider() throws RepositoryException {
+        if (acProvider == null) {
+            acProvider = AccessControlProviderStub.newInstance(config);
+        }
+        return acProvider;
+    }
+
     public LockInfo getLockInfo(NodeId nodeId) throws RepositoryException {
         return service.getLockInfo(sessionInfo, nodeId);
     }
@@ -358,7 +378,7 @@ public class WorkspaceManager
      */
     public void addEventListener(InternalEventListener listener) throws RepositoryException {
         if (changeFeed == null) {
-            changeFeed = createChangeFeed(pollTimeout, observationSupported);
+            changeFeed = createChangeFeed(config.getPollTimeout(), observationSupported);
         }
 
         synchronized (listeners) {
@@ -493,7 +513,7 @@ public class WorkspaceManager
      * @return a new InternalEventListener
      */
     private InternalEventListener createHierarchyListener(HierarchyManager hierarchyMgr) {
-        InternalEventListener listener = new HierarchyEventListener(this, hierarchyMgr, cacheBehaviour);
+        InternalEventListener listener = new HierarchyEventListener(this, hierarchyMgr, config.getCacheBehaviour());
         return listener;
     }
 
@@ -912,6 +932,38 @@ public class WorkspaceManager
         }
 
         /**
+         * @see OperationVisitor#visit(org.apache.jackrabbit.jcr2spi.operation.SetTree)
+         */
+        public void visit(SetTree operation) throws RepositoryException {
+            NodeState treeState = operation.getTreeState();
+            Tree tree = service.createTree(sessionInfo, batch, treeState.getName(), treeState.getNodeTypeName(), treeState.getUniqueID());
+            populateTree(tree, treeState.getNodeEntry());
+            batch.setTree(operation.getParentId(), tree);
+        }
+
+        private void populateTree(Tree tree, NodeEntry nodeEntry) throws RepositoryException {
+            Iterator<PropertyEntry> pEntries = nodeEntry.getPropertyEntries();
+            while (pEntries.hasNext()) {
+                PropertyState ps = pEntries.next().getPropertyState();
+                if (!NameConstants.JCR_PRIMARYTYPE.equals(ps.getName()) && !NameConstants.JCR_UUID.equals(ps.getName())) {
+                    if (ps.isMultiValued()) {
+                        tree.addProperty(ps.getParent().getNodeId(), ps.getName(), ps.getType(), ps.getValues());
+                    } else {
+                        tree.addProperty(ps.getParent().getNodeId(), ps.getName(), ps.getType(), ps.getValue());
+                    }
+                }
+            }
+
+            Iterator<NodeEntry> nEntries = nodeEntry.getNodeEntries();
+            while (nEntries.hasNext()) {
+                NodeEntry child = nEntries.next();
+                NodeState childState = child.getNodeState();
+                Tree childTree = tree.addChild(childState.getName(), childState.getNodeTypeName(), childState.getUniqueID());
+                populateTree(childTree, child);
+            }
+        }
+
+        /**
          * @see OperationVisitor#visit(Clone)
          */
         public void visit(Clone operation) throws NoSuchWorkspaceException, LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/config/RepositoryConfig.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/config/RepositoryConfig.java
index 09d5c6e..5d77203 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/config/RepositoryConfig.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/config/RepositoryConfig.java
@@ -41,4 +41,6 @@ public interface RepositoryConfig {
      * @return  the poll timeout in milliseconds.
      */
     public int getPollTimeout();
+
+    public <T> T getConfiguration(String name, T defaultValue);
 }
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntryImpl.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntryImpl.java
index cf885f6..24e2f2e 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntryImpl.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntryImpl.java
@@ -982,7 +982,7 @@ public class NodeEntryImpl extends HierarchyEntryImpl implements NodeEntry {
     //-------------------------------------------------< HierarchyEntryImpl >---
     /**
      * @see HierarchyEntryImpl#doResolve()
-     * <p/>
+     * <p>
      * Returns a <code>NodeState</code>.
      */
     @Override
@@ -1656,7 +1656,7 @@ public class NodeEntryImpl extends HierarchyEntryImpl implements NodeEntry {
                         log.warn("Reverting didn't restore the correct index.");
                     }
                 } catch (RepositoryException e) {
-                    log.warn("Unable to calculate index.", e.getMessage());
+                    log.warn("Unable to calculate index. {}", e.getMessage());
                 }
             }
             revertInfo = null;
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/PropertyEntryImpl.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/PropertyEntryImpl.java
index a05050f..2902f46 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/PropertyEntryImpl.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/PropertyEntryImpl.java
@@ -61,7 +61,7 @@ public class PropertyEntryImpl extends HierarchyEntryImpl implements PropertyEnt
     //------------------------------------------------------< HierarchyEntryImpl >---
     /**
      * @see HierarchyEntryImpl#doResolve()
-     * <p/>
+     * <p>
      * Returns a <code>PropertyState</code>.
      */
     @Override
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java
index 951f86c..9e53b93 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/lock/LockManagerImpl.java
@@ -309,7 +309,7 @@ public class LockManagerImpl implements LockStateManager, SessionListener {
             // error occurred.
             // for this case, assume that no lock exists and delegate final
             // validation to the spi-implementation.
-            log.warn("Error while accessing lock holding NodeState", e.getMessage());
+            log.warn("Error while accessing lock holding NodeState: {}", e.getMessage());
             return null;
         }
     }
@@ -597,7 +597,7 @@ public class LockManagerImpl implements LockStateManager, SessionListener {
                     PropertyState ps = lockHoldingState.getPropertyState(NameConstants.JCR_LOCKISDEEP);
                     ps.addListener(this);
                 } catch (RepositoryException e) {
-                    log.warn("Unable to retrieve jcr:isDeep property after lock creation.", e.getMessage());
+                    log.warn("Unable to retrieve jcr:isDeep property after lock creation. {}", e.getMessage());
                 }
             }
         }
@@ -833,7 +833,7 @@ public class LockManagerImpl implements LockStateManager, SessionListener {
                     lockState.reloadLockInfo();
                 } catch (RepositoryException e) {
                     // may occur if session has been logged out. rather throw?
-                    log.warn("Unable to determine lock status.", e.getMessage());
+                    log.warn("Unable to determine lock status. {}", e.getMessage());
                 }
             } // else: nothing to do.
         }
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/BitsetENTCacheImpl.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/BitsetENTCacheImpl.java
index 26f9239..a04cc1b 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/BitsetENTCacheImpl.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/BitsetENTCacheImpl.java
@@ -438,8 +438,8 @@ class BitsetENTCacheImpl implements EffectiveNodeTypeCache {
                         long h1 = w1 >>> 32;
                         long h2 = w2 >>> 32;
                         if (h1 == h2) {
-                            h1 = w1 & 0x0ffffL;
-                            h2 = w2 & 0x0ffffL;
+                            h1 = w1 & 0x0ffffffffL;
+                            h2 = w2 & 0x0ffffffffL;
                         }
                         return Long.signum(h2 - h1);
                     }
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/EffectiveNodeTypeCache.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/EffectiveNodeTypeCache.java
index d351936..aed05d0 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/EffectiveNodeTypeCache.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/EffectiveNodeTypeCache.java
@@ -109,7 +109,7 @@ public interface EffectiveNodeTypeCache extends Cloneable {
         /**
          * Creates a new key as a result of a subtract operation. i.e. removes all
          * node type names that from the other key.
-         * <p/>
+         * <p>
          * Please note that no exception is thrown if the other key has node type
          * names that are not contained in this key (i.e. {@link #contains(Key)}
          * returns <code>false</code>).
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/EffectiveNodeTypeImpl.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/EffectiveNodeTypeImpl.java
index 9338e43..6d68ec8 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/EffectiveNodeTypeImpl.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/EffectiveNodeTypeImpl.java
@@ -40,7 +40,7 @@ import org.slf4j.LoggerFactory;
  * An <code>EffectiveNodeType</code> represents one or more
  * <code>NodeType</code>s as one 'effective' node type where inheritance
  * is resolved.
- * <p/>
+ * <p>
  * Instances of <code>EffectiveNodeType</code> are immutable.
  */
 public class EffectiveNodeTypeImpl implements Cloneable, EffectiveNodeType {
@@ -487,7 +487,7 @@ public class EffectiveNodeTypeImpl implements Cloneable, EffectiveNodeType {
     /**
      * Internal helper method which merges another <code>EffectiveNodeType</code>
      * instance with <i>this</i> instance.
-     * <p/>
+     * <p>
      * Warning: This instance might be in an inconsistent state if an exception
      * is thrown.
      *
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/NodeTypeImpl.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/NodeTypeImpl.java
index 37c78a7..5e4ed10 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/NodeTypeImpl.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/nodetype/NodeTypeImpl.java
@@ -56,7 +56,7 @@ public class NodeTypeImpl extends AbstractNodeType implements NodeTypeDefinition
 
     /**
      * Package private constructor
-     * <p/>
+     * <p>
      * Creates a valid node type instance.
      * We assume that the node type definition is valid and all referenced
      * node types (supertypes, required node types etc.) do exist and are valid.
@@ -109,7 +109,7 @@ public class NodeTypeImpl extends AbstractNodeType implements NodeTypeDefinition
     /**
      * Tests if the value constraints defined in the property definition
      * <code>def</code> are satisfied by the the specified <code>values</code>.
-     * <p/>
+     * <p>
      * Note that the <i>protected</i> flag is not checked. Also note that no
      * type conversions are attempted if the type of the given values does not
      * match the required type as specified in the given definition.
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddNode.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddNode.java
index 7dc1fce..91c25b6 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddNode.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddNode.java
@@ -56,9 +56,8 @@ public class AddNode extends TransientOperation {
         this(parentState, nodeName, nodeTypeName, uuid, DEFAULT_OPTIONS);
     }
 
-    private AddNode(NodeState parentState, Name nodeName, Name nodeTypeName,
-                    String uuid, int options)
-            throws RepositoryException {
+    AddNode(NodeState parentState, Name nodeName, Name nodeTypeName,
+            String uuid, int options) throws RepositoryException {
         super(options);
         this.parentId = parentState.getNodeId();
         this.parentState = parentState;
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddProperty.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddProperty.java
index bc50180..c78d797 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddProperty.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/AddProperty.java
@@ -49,10 +49,10 @@ public class AddProperty extends TransientOperation {
                         QPropertyDefinition definition) throws RepositoryException {
         this(parentState, propName, propertyType, values, definition, DEFAULT_OPTIONS);
     }
-    
-    private AddProperty(NodeState parentState, Name propName,
-                        int propertyType, QValue[] values,
-                        QPropertyDefinition definition, int options) throws RepositoryException {
+
+    AddProperty(NodeState parentState, Name propName,
+                int propertyType, QValue[] values,
+                QPropertyDefinition definition, int options) throws RepositoryException {
         super(options);
         this.parentId = parentState.getNodeId();
         this.parentState = parentState;
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/IgnoreOperation.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/IgnoreOperation.java
new file mode 100644
index 0000000..2f71140
--- /dev/null
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/IgnoreOperation.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi.operation;
+
+/**
+ * Marker interface that represent operations which must be ignored
+ * by the <code>SessionItemStateManager</code> for building the final ChangeLog. Instances
+ * of <code>IgnoreOperation</code> never appear in the ChangeLog.
+ */
+public interface IgnoreOperation {
+}
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/Merge.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/Merge.java
index d3edc6c..ee60bd1 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/Merge.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/Merge.java
@@ -91,7 +91,7 @@ public class Merge extends AbstractOperation {
                     vhe.invalidate(true);
                 }
             } catch (RepositoryException e) {
-                log.warn("Error while retrieving VersionHistory entry:", e.getMessage());
+                log.warn("Error while retrieving VersionHistory entry: {}", e.getMessage());
             }
             nodeState.getHierarchyEntry().invalidate(true);
         }
@@ -144,4 +144,4 @@ public class Merge extends AbstractOperation {
     public static Merge create(NodeState nodeState, String srcWorkspaceName, boolean bestEffort, boolean isShallow, VersionManager mgr) {
         return new Merge(nodeState, srcWorkspaceName, bestEffort, isShallow, mgr);
     }
-}
\ No newline at end of file
+}
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/OperationVisitor.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/OperationVisitor.java
index fa2af98..2d575eb 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/OperationVisitor.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/OperationVisitor.java
@@ -51,6 +51,8 @@ public interface OperationVisitor {
 
     public void visit(ReorderNodes operation) throws RepositoryException;
 
+    public void visit(SetTree operation) throws RepositoryException;
+
     public void visit(Clone operation) throws NoSuchWorkspaceException, LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException;
 
     public void visit(Copy operation) throws NoSuchWorkspaceException, LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException;
@@ -102,4 +104,4 @@ public interface OperationVisitor {
      * @since JCR 2.0
      */
     public void visit(CreateConfiguration operation) throws RepositoryException;
-}
\ No newline at end of file
+}
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/Remove.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/Remove.java
index f50352a..3d6b1ee 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/Remove.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/Remove.java
@@ -25,16 +25,12 @@ import org.apache.jackrabbit.jcr2spi.state.ItemState;
 import org.apache.jackrabbit.jcr2spi.state.ItemStateValidator;
 import org.apache.jackrabbit.jcr2spi.state.NodeState;
 import org.apache.jackrabbit.spi.ItemId;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * <code>Remove</code>...
  */
 public class Remove extends TransientOperation {
 
-    private static Logger log = LoggerFactory.getLogger(Remove.class);
-
     private static final int REMOVE_OPTIONS =
             ItemStateValidator.CHECK_LOCK
             | ItemStateValidator.CHECK_VERSIONING
@@ -44,10 +40,6 @@ public class Remove extends TransientOperation {
     protected ItemState removeState;
     protected NodeState parent;
 
-    private Remove(ItemState removeState, NodeState parent) throws RepositoryException {
-        this(removeState, parent, REMOVE_OPTIONS);
-    }
-
     private Remove(ItemState removeState, NodeState parent, int options) throws RepositoryException {
         super(options);
         this.removeId = removeState.getId();
@@ -101,11 +93,15 @@ public class Remove extends TransientOperation {
 
     //------------------------------------------------------------< Factory >---
     public static Operation create(ItemState state) throws RepositoryException {
+        return create(state, REMOVE_OPTIONS);
+    }
+
+    public static Operation create(ItemState state, int options) throws RepositoryException {
         if (state.isNode() && ((NodeState) state).getDefinition().allowsSameNameSiblings()) {
             // in case of SNS-siblings make sure the parent hierarchy entry has
             // its child entries loaded.
             assertChildNodeEntries(state.getParent());
         }
-        return new Remove(state, state.getParent());
+        return new Remove(state, state.getParent(), options);
     }
 }
\ No newline at end of file
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/SetTree.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/SetTree.java
new file mode 100644
index 0000000..3cdccb9
--- /dev/null
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/SetTree.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi.operation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.ItemExistsException;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.ValueFormatException;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.version.VersionException;
+
+import org.apache.jackrabbit.jcr2spi.state.ItemStateValidator;
+import org.apache.jackrabbit.jcr2spi.state.NodeState;
+import org.apache.jackrabbit.jcr2spi.state.UpdatableItemStateManager;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.spi.QPropertyDefinition;
+import org.apache.jackrabbit.spi.QValue;
+
+public class SetTree extends TransientOperation {
+ 
+    /**
+     * List of operations added to this SetTree operation.
+     */
+    private final List<Operation> operations = new ArrayList<Operation>();
+        
+    private final NodeState treeState;
+
+    private SetTree(NodeState treeState) throws RepositoryException {
+        super(ItemStateValidator.CHECK_NONE);
+        this.treeState = treeState;
+    }
+
+    private SetTree(UpdatableItemStateManager itemStateMgr, NodeState parentState, Name nodeName, Name nodeTypeName, String uuid) throws RepositoryException {
+        super(ItemStateValidator.CHECK_NONE);
+        Operation addNode = InternalAddNode.create(parentState, nodeName, nodeTypeName, uuid);
+        operations.add(addNode);
+        
+        itemStateMgr.execute(addNode);
+        treeState = (NodeState) ((AddNode) addNode).getAddedStates().get(0);
+    }
+
+    //-----------------------------------------------------------------< Operation >---
+    /**
+     * @param visitor
+     */
+    public void accept(OperationVisitor visitor) throws ValueFormatException, LockException, ConstraintViolationException, AccessDeniedException, ItemExistsException, UnsupportedRepositoryOperationException, VersionException, RepositoryException {
+        assert status == STATUS_PENDING;
+        visitor.visit(this);
+    }
+
+    /**
+     * Persisting a SetPolicy operation involves persisting each individual operation added
+     * by this policy. The concerned operation will assert the status and set it accordingly.
+     *
+     * @see Operation#persisted()
+     */
+    @Override
+    public void persisted() throws RepositoryException {  
+        assert status == STATUS_PENDING;
+        status = STATUS_PERSISTED;
+        for (Operation op : operations) {
+            op.persisted();
+        }
+    }
+
+    /**
+     * Undoing a SetPolicy operation involves undoing all operations added by the SetPolicy.
+     * @see Operation#undo()
+     */
+    @Override
+    public void undo() throws RepositoryException {
+        assert status == STATUS_PENDING;
+        status = STATUS_PERSISTED;
+        for (Operation op : operations) {
+            op.undo();
+        }
+    }
+
+    public NodeId getParentId() throws RepositoryException {
+        return treeState.getParent().getNodeId();
+    }
+    
+    public NodeState getParentState() throws RepositoryException {
+        return treeState.getParent();
+    }
+
+    public NodeState getTreeState() throws RepositoryException {
+        return treeState;
+    }
+
+    /**
+     * Add a child node operation to this {@code setTree} instance.
+     *
+     * @param parentState
+     * @param nodeName
+     * @param nodeTypeName
+     * @param uuid
+     * @return
+     * @throws RepositoryException
+     */
+    public Operation addChildNode(NodeState parentState, Name nodeName, Name nodeTypeName, String uuid) throws RepositoryException {
+        Operation addNode = InternalAddNode.create(parentState, nodeName, nodeTypeName, uuid);
+        operations.add(addNode);
+        return addNode;
+    }
+    /**
+     * Add a child property operation to this {@code setTree} instance.
+     *
+     * @param parentState
+     * @param propName
+     * @param propertyType
+     * @param values
+     * @param definition
+     * @return
+     * @throws RepositoryException
+     */
+    public Operation addChildProperty(NodeState parentState, Name propName,
+                                      int propertyType, QValue[] values,
+                                      QPropertyDefinition definition) throws RepositoryException {
+        Operation addProperty = new InternalAddProperty(parentState, propName, propertyType, values, definition);
+        operations.add(addProperty);
+        return addProperty;
+    }
+
+    //------------------------------------------------------------< factory >---
+
+    public static SetTree create(NodeState treeState) throws RepositoryException {
+        SetTree operation = new SetTree(treeState);
+        return operation;
+    }
+
+    public static SetTree create(UpdatableItemStateManager itemStateMgr, NodeState parent, Name nodeName, Name nodeTypeName, String uuid) throws RepositoryException {
+        return new SetTree(itemStateMgr, parent, nodeName, nodeTypeName, uuid);
+    }
+
+    //--------------------------------------------------------------------------
+
+    /**
+     * Inner class for adding a protected node.
+     */
+    private static final class InternalAddNode extends AddNode implements IgnoreOperation {
+        /**
+         * Options that must not be violated for a successful set policy operation.
+         */
+        private final static int ADD_NODE_OPTIONS =  ItemStateValidator.CHECK_ACCESS |
+                ItemStateValidator.CHECK_LOCK |
+                ItemStateValidator.CHECK_COLLISION |
+                ItemStateValidator.CHECK_VERSIONING;
+
+        private InternalAddNode(NodeState parentState, Name nodeName, Name nodeTypeName, String uuid) throws RepositoryException {
+            super(parentState, nodeName, nodeTypeName, uuid, ADD_NODE_OPTIONS);
+        }
+
+        public static Operation create(NodeState parentState, Name nodeName, Name nodeTypeName, String uuid) throws RepositoryException {
+            assertChildNodeEntries(parentState);
+            InternalAddNode an = new InternalAddNode(parentState, nodeName, nodeTypeName, uuid);
+            return an;
+        }
+    }
+
+    /**
+     * Inner class for adding a protected property.
+     */
+    private static final class InternalAddProperty extends AddProperty implements IgnoreOperation {
+        private final static int ADD_PROPERTY_OPTIONS =  ItemStateValidator.CHECK_ACCESS |
+                ItemStateValidator.CHECK_LOCK |
+                ItemStateValidator.CHECK_COLLISION |
+                ItemStateValidator.CHECK_VERSIONING;
+
+        private InternalAddProperty(NodeState parentState, Name propName, int propertyType, QValue[] values, QPropertyDefinition definition) throws RepositoryException {
+            super(parentState, propName, propertyType, values, definition, ADD_PROPERTY_OPTIONS);
+        }
+    }
+}
+
+
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/WorkspaceImport.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/WorkspaceImport.java
index 1881446..5d5271f 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/WorkspaceImport.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/operation/WorkspaceImport.java
@@ -1,110 +1,110 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.jcr2spi.operation;
-
-import org.apache.jackrabbit.jcr2spi.state.NodeState;
-import org.apache.jackrabbit.jcr2spi.hierarchy.NodeEntry;
-import org.apache.jackrabbit.spi.NodeId;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.AccessDeniedException;
-import javax.jcr.ItemExistsException;
-import javax.jcr.UnsupportedRepositoryOperationException;
-import javax.jcr.ImportUUIDBehavior;
-import javax.jcr.version.VersionException;
-import javax.jcr.nodetype.ConstraintViolationException;
-import javax.jcr.nodetype.NoSuchNodeTypeException;
-import java.io.InputStream;
-
-/**
- * <code>WorkspaceImport</code>...
- */
-public class WorkspaceImport extends AbstractOperation {
-
-    private final NodeState nodeState;
-    private final InputStream xmlStream;
-    private final int uuidBehaviour;
-
-    private WorkspaceImport(NodeState nodeState, InputStream xmlStream, int uuidBehaviour) {
-        if (nodeState == null || xmlStream == null) {
-            throw new IllegalArgumentException();
-        }
-        this.nodeState = nodeState;
-        this.xmlStream = xmlStream;
-        this.uuidBehaviour = uuidBehaviour;
-
-        // NOTE: affected-states only needed for transient modifications
-    }
-
-    //----------------------------------------------------------< Operation >---
-    /**
-     * @see Operation#accept(OperationVisitor)
-     */
-    public void accept(OperationVisitor visitor) throws RepositoryException, ConstraintViolationException, AccessDeniedException, ItemExistsException, NoSuchNodeTypeException, UnsupportedRepositoryOperationException, VersionException {
-        assert status == STATUS_PENDING;
-        visitor.visit(this);
-    }
-
-    /**
-     * Invalidates the <code>NodeState</code> that has been updated and all
-     * its descendants.
-     *
-     * @see Operation#persisted()
-     */
-    public void persisted() {
-        assert status == STATUS_PENDING;
-        status = STATUS_PERSISTED;
-        NodeEntry entry;
-        if (uuidBehaviour == ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING ||
-                uuidBehaviour == ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING) {
-            // invalidate the complete tree
-            entry = nodeState.getNodeEntry();
-            while (entry.getParent() != null) {
-                entry = entry.getParent();
-            }
-            entry.invalidate(true);
-        } else {
-            // import only added new items below the import target. therefore
-            // recursive invalidation is not required. // TODO correct?
-            nodeState.getNodeEntry().invalidate(false);
-        }
-    }
-
-    //----------------------------------------< Access Operation Parameters >---
-    public NodeId getNodeId() throws RepositoryException {
-        return nodeState.getNodeId();
-    }
-
-    public InputStream getXmlStream() {
-        return xmlStream;
-    }
-
-    public int getUuidBehaviour() {
-        return uuidBehaviour;
-    }
-
-    //------------------------------------------------------------< Factory >---
-    /**
-     *
-     * @param nodeState
-     * @param xmlStream
-     * @return
-     */
-    public static Operation create(NodeState nodeState, InputStream xmlStream, int uuidBehaviour) {
-        return new WorkspaceImport(nodeState, xmlStream, uuidBehaviour);
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi.operation;
+
+import org.apache.jackrabbit.jcr2spi.state.NodeState;
+import org.apache.jackrabbit.jcr2spi.hierarchy.NodeEntry;
+import org.apache.jackrabbit.spi.NodeId;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.AccessDeniedException;
+import javax.jcr.ItemExistsException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.version.VersionException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import java.io.InputStream;
+
+/**
+ * <code>WorkspaceImport</code>...
+ */
+public class WorkspaceImport extends AbstractOperation {
+
+    private final NodeState nodeState;
+    private final InputStream xmlStream;
+    private final int uuidBehaviour;
+
+    private WorkspaceImport(NodeState nodeState, InputStream xmlStream, int uuidBehaviour) {
+        if (nodeState == null || xmlStream == null) {
+            throw new IllegalArgumentException();
+        }
+        this.nodeState = nodeState;
+        this.xmlStream = xmlStream;
+        this.uuidBehaviour = uuidBehaviour;
+
+        // NOTE: affected-states only needed for transient modifications
+    }
+
+    //----------------------------------------------------------< Operation >---
+    /**
+     * @see Operation#accept(OperationVisitor)
+     */
+    public void accept(OperationVisitor visitor) throws RepositoryException, ConstraintViolationException, AccessDeniedException, ItemExistsException, NoSuchNodeTypeException, UnsupportedRepositoryOperationException, VersionException {
+        assert status == STATUS_PENDING;
+        visitor.visit(this);
+    }
+
+    /**
+     * Invalidates the <code>NodeState</code> that has been updated and all
+     * its descendants.
+     *
+     * @see Operation#persisted()
+     */
+    public void persisted() {
+        assert status == STATUS_PENDING;
+        status = STATUS_PERSISTED;
+        NodeEntry entry;
+        if (uuidBehaviour == ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING ||
+                uuidBehaviour == ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING) {
+            // invalidate the complete tree
+            entry = nodeState.getNodeEntry();
+            while (entry.getParent() != null) {
+                entry = entry.getParent();
+            }
+            entry.invalidate(true);
+        } else {
+            // import only added new items below the import target. therefore
+            // recursive invalidation is not required. // TODO correct?
+            nodeState.getNodeEntry().invalidate(false);
+        }
+    }
+
+    //----------------------------------------< Access Operation Parameters >---
+    public NodeId getNodeId() throws RepositoryException {
+        return nodeState.getNodeId();
+    }
+
+    public InputStream getXmlStream() {
+        return xmlStream;
+    }
+
+    public int getUuidBehaviour() {
+        return uuidBehaviour;
+    }
+
+    //------------------------------------------------------------< Factory >---
+    /**
+     *
+     * @param nodeState
+     * @param xmlStream
+     * @return
+     */
+    public static Operation create(NodeState nodeState, InputStream xmlStream, int uuidBehaviour) {
+        return new WorkspaceImport(nodeState, xmlStream, uuidBehaviour);
+    }
+}
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/query/QueryResultImpl.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/query/QueryResultImpl.java
index 475ec69..fa1848f 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/query/QueryResultImpl.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/query/QueryResultImpl.java
@@ -19,8 +19,6 @@ package org.apache.jackrabbit.jcr2spi.query;
 import org.apache.jackrabbit.jcr2spi.ItemManager;
 import org.apache.jackrabbit.jcr2spi.ManagerProvider;
 import org.apache.jackrabbit.spi.QueryInfo;
-import org.apache.jackrabbit.spi.Name;
-import org.apache.jackrabbit.spi.commons.conversion.NameResolver;
 
 import javax.jcr.NodeIterator;
 import javax.jcr.RepositoryException;
@@ -66,13 +64,7 @@ class QueryResultImpl implements QueryResult {
      * {@inheritDoc}
      */
     public String[] getSelectorNames() throws RepositoryException {
-        Name[] names = queryInfo.getSelectorNames();
-        String[] sn = new String[names.length];
-        NameResolver resolver = mgrProvider.getNameResolver();
-        for (int i = 0; i < sn.length; i++) {
-            sn[i] = resolver.getJCRName(names[i]);
-        }
-        return sn;
+        return queryInfo.getSelectorNames();
     }
 
     /**
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/query/RowIteratorImpl.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/query/RowIteratorImpl.java
index 54562c9..2664348 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/query/RowIteratorImpl.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/query/RowIteratorImpl.java
@@ -137,7 +137,7 @@ class RowIteratorImpl implements RowIterator {
      * Returns the current position within this iterator. The number
      * returned is the 0-based index of the next <code>Row</code> in the iterator,
      * i.e. the one that will be returned on the subsequent <code>next</code> call.
-     * <p/>
+     * <p>
      * Note that this method does not check if there is a next element,
      * i.e. an empty iterator will always return 0.
      *
@@ -245,7 +245,7 @@ class RowIteratorImpl implements RowIterator {
 
         /**
          * Returns the value of the indicated  property in this <code>Row</code>.
-         * <p/>
+         * <p>
          * If <code>propertyName</code> is not among the column names of the
          * query result table, an <code>ItemNotFoundException</code> is thrown.
          *
@@ -291,7 +291,7 @@ class RowIteratorImpl implements RowIterator {
          * @see Row#getNode(String)
          */
         public Node getNode(String selectorName) throws RepositoryException {
-            return getNode(row.getNodeId(resolver.getQName(selectorName)));
+            return getNode(row.getNodeId(selectorName));
         }
 
         /**
@@ -329,7 +329,7 @@ class RowIteratorImpl implements RowIterator {
          * @see Row#getScore(String)
          */
         public double getScore(String selectorName) throws RepositoryException {
-            return row.getScore(resolver.getQName(selectorName));
+            return row.getScore(selectorName);
         }
 
         /**
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/AccessControlProvider.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/AccessControlProvider.java
new file mode 100644
index 0000000..081b461
--- /dev/null
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/AccessControlProvider.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi.security.authorization;
+
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.jcr2spi.ItemManager;
+import org.apache.jackrabbit.jcr2spi.config.RepositoryConfig;
+import org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyManager;
+import org.apache.jackrabbit.jcr2spi.nodetype.ItemDefinitionProvider;
+import org.apache.jackrabbit.jcr2spi.state.UpdatableItemStateManager;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.spi.SessionInfo;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+
+/**
+ * AccessControlProvider... TODO
+ */
+public interface AccessControlProvider {
+
+    void init(RepositoryConfig config) throws RepositoryException;
+
+    /**
+     * The privileges corresponding to the specified name.
+     *
+     * @param sessionInfo
+     * @param resolver
+     * @return
+     * @see javax.jcr.security.AccessControlManager#privilegeFromName(String)
+     */
+    Privilege privilegeFromName(SessionInfo sessionInfo, NamePathResolver resolver, String privilegeName) throws RepositoryException;
+
+    /**
+     * Obtain the privileges supported at the specified path.
+     *
+     * @param sessionInfo
+     * @param nodeId The id of an existing node or {@code null} to obtain privileges
+     *               that are supported for repository level access.
+     * @param npResolver
+     * @return
+     * @throws RepositoryException
+     * @see javax.jcr.security.AccessControlManager#getSupportedPrivileges(String)
+     */
+    Map<String, Privilege> getSupportedPrivileges(SessionInfo sessionInfo, NodeId nodeId, NamePathResolver npResolver) throws RepositoryException;
+
+    Set<Privilege> getPrivileges(SessionInfo sessionInfo, NodeId id, NamePathResolver npResolver) throws RepositoryException;
+
+    AccessControlManager createAccessControlManager(SessionInfo sessionInfo,
+                                                    UpdatableItemStateManager itemStateManager,
+                                                    ItemManager itemManager,
+                                                    ItemDefinitionProvider definitionProvider,
+                                                    HierarchyManager hierarchyManager,
+                                                    NamePathResolver npResolver) throws RepositoryException;
+}
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/AccessControlProviderStub.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/AccessControlProviderStub.java
new file mode 100644
index 0000000..94f376f
--- /dev/null
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/AccessControlProviderStub.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi.security.authorization;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+
+import org.apache.jackrabbit.jcr2spi.config.RepositoryConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Stub class that provide clients with access to a concrete
+ * AccessControlProvider implementation. TODO: Explain the way the concrete
+ * provider is located, loaded and instantiated.
+ * 
+ */
+public class AccessControlProviderStub {
+
+	private static Logger log = LoggerFactory.getLogger(AccessControlProviderStub.class);
+
+	/**
+	 * The class property parameter determines the {@link AccessControlProvider}
+	 * to load and instantiate. This is a fall-back parameter if the
+	 * SYS_PROP_AC_PROVIDER_IMPL is not set.
+	 */
+	private static final String ACCESS_CONTROL_PROVIDER_PROPERTIES = "accessControlProvider.properties";
+
+	/**
+	 * Key look-up.
+	 */
+	private static final String PROPERTY_ACCESSCONTROL_PROVIDER_CLASS = "org.apache.jackrabbit.jcr2spi.AccessControlProvider.class";
+
+	/**
+	 * Avoid instantiation.
+	 */
+	private AccessControlProviderStub() {
+	}
+
+	/**
+	 * Instantiates and returns a concrete AccessControlProvider implementation.
+	 * 
+	 * @param service
+	 *            The repository service.
+	 * @param config
+	 *            The RepositoryConfig to read configuration parameters.
+	 * @return
+	 * @throws RepositoryException
+	 */
+	public static AccessControlProvider newInstance(RepositoryConfig config) throws RepositoryException {
+
+		String className = getProviderClass(config);
+		if (className != null) {
+			try {
+				Class<?> acProviderClass = Class.forName(className);
+				if (AccessControlProvider.class.isAssignableFrom(acProviderClass)) {
+					AccessControlProvider acProvider = (AccessControlProvider) acProviderClass.newInstance();
+					acProvider.init(config);
+					return acProvider;
+				} else {
+					throw new RepositoryException("Fail to create AccessControlProvider from configuration.");
+				}
+			} catch (Exception e) {
+				throw new RepositoryException("Fail to create AccessControlProvider from configuration.");
+			}
+		}
+
+		// ac not supported in this setup.
+		throw new UnsupportedRepositoryOperationException("Access control is not supported");
+	}
+
+	private static String getProviderClass(RepositoryConfig config) throws RepositoryException {
+
+		String implClass = config.getConfiguration(PROPERTY_ACCESSCONTROL_PROVIDER_CLASS, null);
+
+		if (implClass != null) {
+			return implClass;
+		} else {
+			try {
+				// not configured try to load as resource
+				Properties prop = new Properties();
+				InputStream is = AccessControlProviderStub.class.getClassLoader().getResourceAsStream(ACCESS_CONTROL_PROVIDER_PROPERTIES);
+				if (is != null) {
+					prop.load(is);
+					// loads the concrete class to instantiate.
+					if (prop.containsKey(PROPERTY_ACCESSCONTROL_PROVIDER_CLASS)) {
+						return prop.getProperty(PROPERTY_ACCESSCONTROL_PROVIDER_CLASS);
+					} else {
+						log.debug("Missing AccessControlProvider configuration.");
+					}
+				} else {
+					log.debug("Fail to locate the access control provider properties file.");
+				}
+			} catch (IOException e) {
+				throw new RepositoryException("Fail to load AccessControlProvider configuration.");
+			}
+		}
+		return null;
+	}
+}
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/PrivilegeImpl.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/PrivilegeImpl.java
new file mode 100644
index 0000000..7453754
--- /dev/null
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/PrivilegeImpl.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi.security.authorization;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.jcr.NamespaceException;
+import javax.jcr.RepositoryException;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.PrivilegeDefinition;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+
+public class PrivilegeImpl implements Privilege {
+
+    private final PrivilegeDefinition definition;
+
+    private final Privilege[] declaredAggregates;
+    private final Privilege[] aggregates;
+    private static final Privilege[] EMPTY_ARRAY = new Privilege[0];
+
+    private final NamePathResolver npResolver;
+
+    public PrivilegeImpl(PrivilegeDefinition definition, PrivilegeDefinition[] allDefs, NamePathResolver npResolver) throws RepositoryException {
+        this.definition = definition;
+        this.npResolver = npResolver;
+
+        Set<Name> set = definition.getDeclaredAggregateNames();
+        Name[] declAggrNames = set.toArray(new Name[set.size()]);
+        if (declAggrNames.length == 0) {
+            declaredAggregates = EMPTY_ARRAY;
+            aggregates = EMPTY_ARRAY;
+        } else {
+            declaredAggregates = new Privilege[declAggrNames.length];
+            for (int i = 0; i < declAggrNames.length; i++) {
+                for (PrivilegeDefinition def : allDefs) {
+                    if (def.getName().equals(declAggrNames[i])) {
+                        declaredAggregates[i] = new PrivilegeImpl(def, allDefs, npResolver);
+                    }
+                }
+            }
+
+            Set<Privilege> aggr = new HashSet<Privilege>();
+            for (Privilege decl : declaredAggregates) {
+                aggr.add(decl);
+                if (decl.isAggregate()) {
+                    aggr.addAll(Arrays.asList(decl.getAggregatePrivileges()));
+                }
+            }
+            aggregates = aggr.toArray(new Privilege[aggr.size()]);
+        }
+    }
+
+    /**
+     * @see Privilege#getName()
+     */
+    public String getName() {
+        try {
+            return npResolver.getJCRName(definition.getName());
+        } catch (NamespaceException e) {
+            // should not occur -> return internal name representation.
+            return definition.getName().toString();
+        }
+    }
+
+    /**
+     * @see Privilege#isAbstract()
+     */
+    public boolean isAbstract() {
+        return definition.isAbstract();
+    }
+
+    /**
+     * @see Privilege#isAggregate()
+     */
+    public boolean isAggregate() {
+        return declaredAggregates.length > 0;
+    }
+
+    /**
+     * @see Privilege#getDeclaredAggregatePrivileges()
+     */
+    public Privilege[] getDeclaredAggregatePrivileges() {
+        return declaredAggregates;
+    }
+
+    /**
+     * @see Privilege#getAggregatePrivileges()
+     */
+    public Privilege[] getAggregatePrivileges() {
+        return aggregates;
+    }
+
+    //---------------------------------------------------------< Object >---
+    @Override
+    public String toString() {
+        return getName();
+    }
+
+    @Override
+    public int hashCode() {
+        return definition.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj instanceof PrivilegeImpl) {
+            PrivilegeImpl other = (PrivilegeImpl) obj;
+            return definition.equals(other.definition);
+        }
+        return false;
+    }
+}
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/AccessControlConstants.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/AccessControlConstants.java
new file mode 100644
index 0000000..45ae7b3
--- /dev/null
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/AccessControlConstants.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi.security.authorization.jackrabbit;
+
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+
+public interface AccessControlConstants {
+    /**
+     * Default name for a node of type rep:Policy.
+     */
+    Name N_POLICY = NameConstants.REP_POLICY;
+    /**
+     * Name for a node of type 'rep:repoPolicy'.
+     */
+    Name N_REPO_POLICY = NameConstants.REP_REPO_POLICY;
+    /**
+     * rep:RepoAccessControllable node type.
+     */
+    Name NT_REP_REPO_ACCESS_CONTROLLABLE = NameConstants.REP_REPO_ACCESS_CONTROLLABLE;
+    /**
+     * rep:AccessControllable nodetype
+     */
+    Name NT_REP_ACCESS_CONTROLLABLE = NameConstants.REP_ACCESS_CONTROLLABLE;
+    
+    Name NT_REP_GRANT_ACE = NameConstants.REP_GRANT_ACE;
+    
+    Name NT_REP_DENY_ACE = NameConstants.REP_DENY_ACE;
+
+    Name NT_REP_ACL = NameConstants.REP_ACL;
+    
+    /**
+     * rep:principalName
+     */
+    Name N_REP_PRINCIPAL_NAME = NameConstants.REP_PRINCIPAL_NAME;
+    
+    /**
+     * rep:glob property name used to restrict the number of child nodes
+     * or properties that are affected by an ACL inherited from a parent node.
+     */
+    Name P_GLOB = NameConstants.REP_GLOB;
+    
+    /**
+     * rep:privileges
+     */
+    Name N_REP_PRIVILEGES = NameConstants.REP_PRIVILEGES;
+
+}
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/AccessControlEntryImpl.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/AccessControlEntryImpl.java
new file mode 100644
index 0000000..f1affbe
--- /dev/null
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/AccessControlEntryImpl.java
@@ -0,0 +1,227 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi.security.authorization.jackrabbit.acl;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.spi.QValueFactory;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+import org.apache.jackrabbit.spi.commons.value.QValueValue;
+import org.apache.jackrabbit.spi.commons.value.ValueFactoryQImpl;
+import org.apache.jackrabbit.value.ValueHelper;
+
+class AccessControlEntryImpl implements JackrabbitAccessControlEntry {
+
+    /*
+     * The principal this entry has been created for.
+     */
+    private final Principal principal;
+    
+    /*
+     * The privileges in this entry.
+     */
+    private final Privilege[] privileges;
+        
+    /*
+     * Whether this entry is allowed/denied
+     */
+    private final boolean isAllow;
+    
+    /*
+     * Restrictions that may apply with this entry.
+     */
+    private final Map<Name, QValue> restrictions;
+    private final Map<Name, Iterable<QValue>> mvRestrictions;
+
+    private final NamePathResolver resolver;
+
+    private final QValueFactory qvf;
+
+    private int hashCode = -1;
+    private int privsHashCode = -1;
+
+    /**
+     * 
+     * @param principal
+     * @param privileges
+     * @param isAllow
+     * @param restrictions
+     * @throws RepositoryException 
+     */
+    AccessControlEntryImpl(Principal principal, Privilege[] privileges, boolean isAllow,
+                           Map<Name, QValue> restrictions, Map<Name, Iterable<QValue>> mvRestrictions,
+                           NamePathResolver resolver, QValueFactory qvf) throws RepositoryException {
+        if (principal == null || (privileges != null && privileges.length == 0)) {
+            throw new AccessControlException("An Entry must not have a NULL principal or empty privileges");
+        }
+        checkAbstract(privileges);
+        
+        this.principal = principal;
+        this.privileges = privileges;
+        this.isAllow = isAllow;
+        this.resolver = resolver;
+        this.qvf = qvf;
+
+        if (restrictions == null) {
+            this.restrictions = Collections.<Name, QValue>emptyMap();
+        } else {
+            this.restrictions = restrictions;
+        }
+        if (mvRestrictions == null) {
+            this.mvRestrictions = Collections.emptyMap();
+        } else {
+            this.mvRestrictions = mvRestrictions;
+        }
+    }
+    
+    @Override
+    public Principal getPrincipal() {
+        return principal;
+    }
+
+    @Override
+    public Privilege[] getPrivileges() {
+        return privileges;
+    }
+    
+    @Override
+    public boolean isAllow() {
+        return isAllow;
+    }
+    
+    @Override
+    public String[] getRestrictionNames() throws RepositoryException {
+        List<String> restNames = new ArrayList<String>(restrictions.size());
+        for (Name restName : restrictions.keySet()) {
+            restNames.add(resolver.getJCRName(restName));
+        }
+        return restNames.toArray(new String[restNames.size()]);
+    }
+
+    @Override
+    public Value getRestriction(String restrictionName)
+            throws ValueFormatException, RepositoryException {
+        try {
+            Name restName = resolver.getQName(restrictionName);
+            if (!restrictions.containsKey(restName)) {
+                return null;
+            }
+            return createJcrValue(restrictions.get(restName));
+        } catch (IllegalStateException e) {
+            throw new RepositoryException(e.getMessage());
+        }
+    }
+
+    /*
+     * As of Jackrabbit 2.8, this extention has been added to the Jackrabbit API.
+     * However, Jackrabbit (before) OAK doesn't support mv. restrictions. Thus simply
+     * return an array containing the single restriction value.
+     */
+    @Override
+    public Value[] getRestrictions(String restrictionName)
+            throws RepositoryException {
+        return new Value[] {getRestriction(restrictionName)};
+    }
+
+    //-------------------------------------------------------------< Object >---
+    @Override
+    public int hashCode() {
+        if (hashCode == -1) {
+            hashCode = buildHashCode();
+        }
+        return hashCode;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj instanceof AccessControlEntryImpl) {
+            AccessControlEntryImpl other = (AccessControlEntryImpl) obj;
+            return principal.getName().equals(other.principal.getName()) &&
+                    isAllow == other.isAllow &&
+                    restrictions.equals(other.restrictions) &&
+                    mvRestrictions.equals(other.mvRestrictions) &&
+                    getPrivilegesHashCode() == other.getPrivilegesHashCode();
+        }
+        return false;
+    }
+
+    //-------------------------------------------------------------< private >---
+    private int buildHashCode() {
+        int h = 17;
+        h = 37 * h + principal.getName().hashCode();
+        h = 37 * h + getPrivilegesHashCode();
+        h = 37 * h + Boolean.valueOf(isAllow).hashCode();
+        h = 37 * h + restrictions.hashCode();
+        h = 37 * h + mvRestrictions.hashCode();
+        return h;
+    }
+
+    private int getPrivilegesHashCode() {
+        if (privsHashCode == -1) {
+            Set<Privilege> prvs = new HashSet<Privilege>(Arrays.asList(privileges));
+            for (Privilege p : privileges) {
+                if (p.isAggregate()) {
+                    prvs.addAll(Arrays.asList(p.getAggregatePrivileges()));
+                }
+            }
+            privsHashCode = prvs.hashCode();
+        }
+        return privsHashCode;
+    }
+
+    private void checkAbstract(Privilege[] privileges) throws AccessControlException {
+        for (Privilege privilege : privileges) {
+            if (privilege.isAbstract()) {
+                throw new AccessControlException("An Entry cannot contain abstract privileges.");
+            }
+        }
+    }
+
+    /**
+     * Creates a jcr Value from the given qvalue using the specified
+     * factory.
+     * @return         the jcr value representing the qvalue.
+     */
+    private Value createJcrValue(QValue qValue) throws RepositoryException {
+        
+        // build ValueFactory
+        ValueFactoryQImpl valueFactory = new ValueFactoryQImpl(qvf, resolver);
+
+        // build jcr value
+        QValueValue jcrValue = new QValueValue(qValue, resolver);
+        
+        return ValueHelper.copy(jcrValue, valueFactory);
+    }
+}
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/AccessControlListImpl.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/AccessControlListImpl.java
new file mode 100644
index 0000000..24ed142
--- /dev/null
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/AccessControlListImpl.java
@@ -0,0 +1,276 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi.security.authorization.jackrabbit.acl;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.NamespaceException;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Value;
+import javax.jcr.security.AccessControlEntry;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.jcr2spi.hierarchy.NodeEntry;
+import org.apache.jackrabbit.jcr2spi.security.authorization.jackrabbit.AccessControlConstants;
+import org.apache.jackrabbit.jcr2spi.state.NodeState;
+import org.apache.jackrabbit.jcr2spi.state.PropertyState;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.spi.QValueFactory;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+import org.apache.jackrabbit.spi.commons.value.ValueFormat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class AccessControlListImpl implements JackrabbitAccessControlList, AccessControlConstants {
+
+    private static final Logger log = LoggerFactory.getLogger(AccessControlListImpl.class);
+
+    private final String jcrPath;
+
+    private final List<AccessControlEntry> entries;
+    private final QValueFactory qValueFactory;
+    private final NamePathResolver resolver;
+
+    /**
+     * Namespace sensitive name of the REP_GLOB property in standard JCR form.
+     */
+    private final String jcrRepGlob;
+
+    public AccessControlListImpl(String jcrPath, NamePathResolver resolver, QValueFactory qValueFactory) throws RepositoryException {
+        this.jcrPath = jcrPath;
+        this.entries = new ArrayList<AccessControlEntry>();
+        this.qValueFactory = qValueFactory;
+        this.resolver = resolver;
+
+        try {
+            jcrRepGlob = resolver.getJCRName(P_GLOB);
+        } catch (NamespaceException e) {
+            throw new RepositoryException(e.getMessage());
+        }
+    }
+
+    AccessControlListImpl(NodeState aclNode, String aclPath, NamePathResolver resolver, QValueFactory factory, AccessControlManager acm) throws RepositoryException {
+        this(aclPath, resolver, factory);
+
+        NodeEntry entry = (NodeEntry) aclNode.getHierarchyEntry();
+        Iterator<NodeEntry> it = entry.getNodeEntries();
+
+        while(it.hasNext()) {
+            NodeState aceNode = it.next().getNodeState();
+            try {
+
+                PropertyState ps = aceNode.getPropertyState(N_REP_PRINCIPAL_NAME);
+
+                // rep:principal property
+                String principalName = ps.getValue().getString();
+                Principal principal = createPrincipal(principalName);
+
+                // rep:privileges property
+                ps = aceNode.getPropertyState(N_REP_PRIVILEGES);
+
+                QValue[] values = ps.getValues();
+                Privilege[] privileges = new Privilege[values.length];
+                for (int i = 0; i < values.length; i++) {
+                    privileges[i] = acm.privilegeFromName(values[i].getString());
+                }
+
+                // rep:glob property -> restrictions
+                Map<Name, QValue> restrictions = null;
+                if (aceNode.hasPropertyName(P_GLOB)) {
+                    ps = aceNode.getPropertyState(P_GLOB);
+                    restrictions = Collections.singletonMap(ps.getName(), ps.getValue());
+                }
+
+                // the isAllow flag
+                boolean isAllow = NT_REP_GRANT_ACE.equals(aceNode.getNodeTypeName());
+                // build the entry
+                AccessControlEntry ace = new AccessControlEntryImpl(principal, privileges, isAllow, restrictions, Collections.EMPTY_MAP, resolver, qValueFactory);
+                entries.add(ace);
+            } catch (RepositoryException e) {
+                log.debug("Fail to create Entry for "+ aceNode.getName().toString());
+            }
+        }
+    }
+
+    //--------------------------------------------------< AccessControlList >---
+    @Override
+    public AccessControlEntry[] getAccessControlEntries()
+            throws RepositoryException {
+        return entries.toArray(new AccessControlEntry[entries.size()]);
+    }
+
+    @Override
+    public boolean addAccessControlEntry(Principal principal, Privilege[] privileges) throws AccessControlException, RepositoryException {
+        return addEntry(principal, privileges, true, Collections.<String, Value>emptyMap());
+    }
+
+    @Override
+    public void removeAccessControlEntry(AccessControlEntry ace)
+            throws AccessControlException, RepositoryException {
+        if (entries.contains(ace)) {
+            entries.remove(ace);
+        } else {
+            throw new AccessControlException("Entry not present in this list");
+        }
+    }
+
+    //----------------------------------------< JackrabbitAccessControlList >---
+    @Override
+    public String getPath() {
+        return jcrPath;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return entries.isEmpty();
+    }
+
+    @Override
+    public int size() {
+        return entries.size();
+    }
+
+    @Override
+    public boolean addEntry(Principal principal, Privilege[] privileges,
+                            boolean isAllow) throws AccessControlException, RepositoryException {
+        return addEntry(principal, privileges, isAllow, Collections.<String, Value>emptyMap());
+    }
+
+    @Override
+    public boolean addEntry(Principal principal, Privilege[] privileges,
+                            boolean isAllow, Map<String, Value> restrictions,
+                            Map<String, Value[]> mvRestrictions) throws AccessControlException,
+            RepositoryException {
+
+
+        // create entry to be added
+        Map<Name, QValue> rs = createRestrictions(restrictions);
+        Map<Name, Iterable<QValue>> mvRs = createMvRestrictions(mvRestrictions);
+        AccessControlEntry entry = createEntry(principal, privileges, isAllow, rs, mvRs);
+
+        return entries.add(entry);
+
+    }
+
+    @Override
+    public boolean addEntry(Principal principal, Privilege[] privileges,
+                            boolean isAllow, Map<String, Value> restrictions)
+            throws AccessControlException, RepositoryException {
+        return addEntry(principal, privileges, isAllow, restrictions, Collections.EMPTY_MAP);
+    }
+
+    @Override
+    public String[] getRestrictionNames() throws RepositoryException {
+        return new String[] {jcrRepGlob};
+    }
+
+    @Override
+    public int getRestrictionType(String restrictionName) throws RepositoryException {
+        if (!jcrRepGlob.equals(restrictionName)) {
+            // JR2 feature
+            return PropertyType.UNDEFINED;
+        }
+        return PropertyType.STRING;
+    }
+
+    @Override
+    public void orderBefore(AccessControlEntry srcEntry,
+                            AccessControlEntry destEntry) throws AccessControlException,
+            UnsupportedRepositoryOperationException, RepositoryException {
+        // TODO
+        throw new UnsupportedRepositoryOperationException("not yet implemented");
+    }
+
+    //-------------------------------------------------------------< Object >---
+    /**
+     * Returns zero to satisfy the Object equals/hashCode contract.
+     * This class is mutable and not meant to be used as a hash key.
+     *
+     * @return always zero
+     * @see Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        return 0;
+    }
+
+    /**
+     * Returns true if the path and the entries are equal; false otherwise.
+     *
+     * @param obj Object to be tested.
+     * @return true if the path and the entries are equal; false otherwise.
+     * @see Object#equals(Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+
+        if (obj instanceof AccessControlListImpl) {
+            AccessControlListImpl acl = (AccessControlListImpl) obj;
+            return jcrPath.equals(acl.jcrPath) && entries.equals(acl.entries);
+        }
+        return false;
+    }
+
+    //------------------------------------------------------------< private >---
+    private AccessControlEntry createEntry(Principal principal, Privilege[] privileges, boolean isAllow,
+                                           Map<Name, QValue> restrictions, Map<Name, Iterable<QValue>> mvRestrictions) throws RepositoryException {
+        return new AccessControlEntryImpl(principal, privileges, isAllow, restrictions, mvRestrictions, resolver, qValueFactory);
+    }
+
+    private Map<Name, QValue> createRestrictions(Map<String, Value> restrictions) throws RepositoryException {
+        Map<Name, QValue> rs = new HashMap<Name, QValue>(restrictions.size());
+        for (String restName : restrictions.keySet()) {
+            Value v = restrictions.get(restName);
+            rs.put(resolver.getQName(restName), ValueFormat.getQValue(v, resolver, qValueFactory));
+        }
+        return rs;
+    }
+
+    private Map<Name, Iterable<QValue>> createMvRestrictions(Map<String, Value[]> restrictions) throws RepositoryException {
+            Map<Name, Iterable<QValue>> rs = new HashMap<Name, Iterable<QValue>>(restrictions.size());
+            for (String restName : restrictions.keySet()) {
+                QValue[] qvs = ValueFormat.getQValues(restrictions.get(restName), resolver, qValueFactory);
+                rs.put(resolver.getQName(restName), Arrays.asList(qvs));
+            }
+            return rs;
+        }
+
+    private static Principal createPrincipal(final String name) {
+        return new Principal() {
+            @Override
+            public String getName() {
+                return name;
+            }
+        };
+    }
+}
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/AccessControlManagerImpl.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/AccessControlManagerImpl.java
new file mode 100644
index 0000000..985165b
--- /dev/null
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/AccessControlManagerImpl.java
@@ -0,0 +1,440 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi.security.authorization.jackrabbit.acl;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.AccessDeniedException;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.ValueFormatException;
+import javax.jcr.security.AccessControlEntry;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.AccessControlList;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.AccessControlPolicyIterator;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.commons.iterator.AccessControlPolicyIteratorAdapter;
+import org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyManager;
+import org.apache.jackrabbit.jcr2spi.hierarchy.NodeEntry;
+import org.apache.jackrabbit.jcr2spi.nodetype.ItemDefinitionProvider;
+import org.apache.jackrabbit.jcr2spi.operation.AddNode;
+import org.apache.jackrabbit.jcr2spi.operation.Operation;
+import org.apache.jackrabbit.jcr2spi.operation.Remove;
+import org.apache.jackrabbit.jcr2spi.operation.SetMixin;
+import org.apache.jackrabbit.jcr2spi.operation.SetTree;
+import org.apache.jackrabbit.jcr2spi.security.authorization.AccessControlProvider;
+import org.apache.jackrabbit.jcr2spi.security.authorization.jackrabbit.AccessControlConstants;
+import org.apache.jackrabbit.jcr2spi.state.ItemStateValidator;
+import org.apache.jackrabbit.jcr2spi.state.NodeState;
+import org.apache.jackrabbit.jcr2spi.state.UpdatableItemStateManager;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.Path;
+import org.apache.jackrabbit.spi.QPropertyDefinition;
+import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.spi.QValueFactory;
+import org.apache.jackrabbit.spi.SessionInfo;
+import org.apache.jackrabbit.spi.commons.conversion.NameException;
+import org.apache.jackrabbit.spi.commons.conversion.NameParser;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Jackrabbit-core specific implementation of the {@code AccessControlManager}.
+ */
+class AccessControlManagerImpl implements AccessControlManager, AccessControlConstants {
+
+    private static final Logger log = LoggerFactory.getLogger(AccessControlManagerImpl.class);
+
+    private static int REMOVE_POLICY_OPTIONS =
+            ItemStateValidator.CHECK_ACCESS |
+            ItemStateValidator.CHECK_LOCK |
+            ItemStateValidator.CHECK_COLLISION |
+            ItemStateValidator.CHECK_VERSIONING;
+    
+    private final SessionInfo sessionInfo;
+    private final HierarchyManager hierarchyManager;
+    private final NamePathResolver npResolver;
+    private final QValueFactory qvf;
+    private final AccessControlProvider acProvider;
+    private final UpdatableItemStateManager itemStateMgr;
+    private final ItemDefinitionProvider definitionProvider;
+    
+    AccessControlManagerImpl(SessionInfo sessionInfo,
+                             UpdatableItemStateManager itemStateMgr,
+                             ItemDefinitionProvider definitionProvider,
+                             HierarchyManager hierarchyManager,
+                             NamePathResolver npResolver,
+                             QValueFactory qvf,
+                             AccessControlProvider acProvider) {
+        this.sessionInfo = sessionInfo;
+        this.hierarchyManager = hierarchyManager;
+        this.itemStateMgr = itemStateMgr;
+        this.npResolver = npResolver;
+        this.qvf = qvf;
+        this.acProvider = acProvider;
+        this.definitionProvider = definitionProvider;
+    }
+
+    public Privilege[] getSupportedPrivileges(String absPath) throws PathNotFoundException, RepositoryException {
+        NodeState state = getNodeState(npResolver.getQPath(absPath));
+        Map<String, Privilege> privileges = acProvider.getSupportedPrivileges(sessionInfo, state.getNodeId(), npResolver);
+        return privileges.values().toArray(new Privilege[privileges.size()]);
+    }
+
+    public Privilege privilegeFromName(String privilegeName) throws AccessControlException, RepositoryException {
+        return acProvider.privilegeFromName(sessionInfo, npResolver, privilegeName);
+    }
+
+    public boolean hasPrivileges(String absPath, Privilege[] privileges) throws PathNotFoundException, RepositoryException {
+        Set<Privilege> privs = acProvider.getPrivileges(sessionInfo, getNodeState(npResolver.getQPath(absPath)).getNodeId(), npResolver);
+        List<Privilege> toTest = Arrays.asList(privileges);
+        if (privs.containsAll(toTest)) {
+            return true;
+        } else {
+            Set<Privilege> agg = new HashSet<Privilege>(privs);
+            for (Privilege p : privs) {
+                if (p.isAggregate()) {
+                    agg.addAll(Arrays.asList(p.getAggregatePrivileges()));
+                }
+            }
+            return agg.containsAll(toTest);
+        }
+    }
+
+    public Privilege[] getPrivileges(String absPath) throws PathNotFoundException, RepositoryException {
+        Set<Privilege> privs = acProvider.getPrivileges(sessionInfo, getNodeState(npResolver.getQPath(absPath)).getNodeId(), npResolver);
+        return privs.toArray(new Privilege[privs.size()]);
+    }
+
+    public AccessControlPolicy[] getEffectivePolicies(String absPath) throws RepositoryException {
+        checkValidNodePath(absPath);
+        checkAccessControlRead(absPath);
+
+        // TODO : add proper implementation
+        return new AccessControlPolicy[] {new AccessControlPolicy() {}};
+    }
+
+    public AccessControlPolicyIterator getApplicablePolicies(String absPath) throws RepositoryException {
+        checkValidNodePath(absPath);
+
+        AccessControlPolicy[] applicable = getApplicable(absPath);
+        if (applicable != null && applicable.length > 0) {
+            return new AccessControlPolicyIteratorAdapter(Arrays.asList(applicable));
+        } else {
+            return AccessControlPolicyIteratorAdapter.EMPTY;
+        }
+    }
+
+    public AccessControlPolicy[] getPolicies(String absPath) throws RepositoryException {
+        checkValidNodePath(absPath);
+
+        List<AccessControlList> policies = new ArrayList<AccessControlList>();
+        NodeState aclNode = getAclNode(absPath);
+        AccessControlList acl;
+        
+        if (aclNode != null) {
+            acl = new AccessControlListImpl(aclNode, absPath, npResolver, qvf, this);
+            policies.add(acl);
+        }
+        return policies.toArray(new AccessControlList[policies.size()]);
+    }
+
+    public void setPolicy(String absPath, AccessControlPolicy policy) throws RepositoryException {
+        checkValidNodePath(absPath);
+        checkValidPolicy(policy);
+        checkAcccessControlItem(absPath);
+
+        SetTree operation;
+        NodeState aclNode = getAclNode(absPath);
+        if (aclNode == null) {
+            // policy node doesn't exist at absPath -> create one.
+            Name name = (absPath == null) ? N_REPO_POLICY : N_POLICY;
+
+            NodeState parent = null;
+            Name mixinType = null;
+            if (absPath == null) {
+                parent = getRootNodeState();
+                mixinType = NT_REP_REPO_ACCESS_CONTROLLABLE;
+            } else {
+                parent = getNodeState(absPath);
+                mixinType = NT_REP_ACCESS_CONTROLLABLE;
+            }
+            setMixin(parent, mixinType);
+
+            operation = SetTree.create(itemStateMgr, parent, name, NT_REP_ACL, null);            
+            aclNode = operation.getTreeState();
+        } else {
+            Iterator<NodeEntry> it = getNodeEntry(aclNode).getNodeEntries();
+            while(it.hasNext()) {
+                it.next().transientRemove();
+            }
+            operation = SetTree.create(aclNode);
+        }
+
+        // create the entry nodes
+        for (AccessControlEntry entry : ((AccessControlListImpl) policy).getAccessControlEntries()) {
+            createAceNode(operation, aclNode, entry);
+        }
+
+        itemStateMgr.execute(operation);
+    }
+        
+    public void removePolicy(String absPath, AccessControlPolicy policy) throws RepositoryException {
+        checkValidNodePath(absPath);
+        checkValidPolicy(policy);
+        
+        NodeState aclNode = getAclNode(absPath);        
+        if (aclNode != null) {
+            removeNode(aclNode);
+        } else {
+            throw new AccessControlException("No policy exist at "+absPath);
+        }
+    }
+
+    //--------------------------------------------------< private >---
+    private AccessControlPolicy[] getApplicable(String absPath) throws RepositoryException {
+        NodeState controlledState;
+        if (absPath == null) {
+            controlledState = getRootNodeState();
+        } else {
+            controlledState = getNodeState(absPath);
+        }
+        
+        AccessControlPolicy acl = null;
+        NodeState aclNode = getAclNode(controlledState, absPath);
+        if (aclNode == null) {
+            acl = new AccessControlListImpl(absPath, npResolver, qvf);
+        }
+        
+        return (acl == null) ? new AccessControlPolicy[0] : new AccessControlPolicy[] {acl};
+    }
+
+    private NodeState getAclNode(String controlledNodePath) throws RepositoryException {
+        NodeState controlledNode;
+        if (controlledNodePath == null) {
+            controlledNode = getRootNodeState();
+        } else {
+            controlledNode = getNodeState(controlledNodePath);
+        }
+        return getAclNode(controlledNode, controlledNodePath);
+    }
+    
+    private NodeState getAclNode(NodeState aclNode, String controlledNodePath) throws RepositoryException {
+        NodeState acl = null;
+        if (controlledNodePath == null) {
+            if (isRepoAccessControlled(aclNode)) {
+                acl = aclNode.getChildNodeState(N_REPO_POLICY, 1);
+            }
+        } else {
+            if (isAccessControlled(aclNode)) {
+                acl = aclNode.getChildNodeState(N_POLICY, 1);
+            }
+        }
+        return acl;
+    }
+        
+    /**
+     * Test if the given node state is of node type
+     * {@link AccessControlConstants#NT_REP_REPO_ACCESS_CONTROLLABLE}
+     * and if it has a child node named
+     * {@link AccessControlConstants#N_REPO_POLICY}.
+     *
+     * @param      nodeState the node state to be tested
+     * @return     <code>true</code> if the node is access controlled and has a
+     *             rep:policy child; <code>false</code> otherwise.
+     * @throws     RepositoryException if an error occurs
+     */
+    private boolean isRepoAccessControlled(NodeState nodeState) throws RepositoryException {
+        return isNodeType(nodeState, NT_REP_REPO_ACCESS_CONTROLLABLE) &&
+               nodeState.hasChildNodeEntry(N_REPO_POLICY, 1);
+    }
+ 
+    private boolean isAccessControlled(NodeState nodeState) throws RepositoryException {
+        return isNodeType(nodeState, NT_REP_ACCESS_CONTROLLABLE) &&
+               nodeState.hasChildNodeEntry(N_POLICY, 1);
+    }
+    
+    /**
+     * Checks if the given node state has the specified mixin.
+     * NOTE: we take the transiently added mixins
+     *       into consideration e.g if added during
+     *       a setPolicies call and the changes are yet to be saved.
+     * @param nodeState
+     * @param mixinName
+     */
+    private boolean isNodeType(NodeState nodeState, Name mixinName) throws RepositoryException {
+        List<Name> lst = Arrays.asList(nodeState.getAllNodeTypeNames());
+        return (lst == null) ? false : lst.contains(mixinName);
+    }
+
+    /**
+     * Checks whether if the given nodePath points to an access
+     * control policy or entry node.
+     * @param nodePath
+     * @throws AccessControlException
+     * @throws RepositoryException
+     */
+    private void checkAcccessControlItem(String nodePath) throws AccessControlException, RepositoryException {
+        NodeState controlledState = getNodeState(nodePath);
+        Name ntName = controlledState.getNodeTypeName();
+        boolean isAcItem =  ntName.equals(NT_REP_ACL) ||
+                            ntName.equals(NT_REP_GRANT_ACE) ||
+                            ntName.equals(NT_REP_DENY_ACE);
+        if (isAcItem) {
+            throw new AccessControlException("The path: "+nodePath+" points to an access control content node");
+        }
+    }
+
+    private void checkAccessControlRead(String absPath) throws RepositoryException {
+        if (!hasPrivileges(absPath, new Privilege[] {privilegeFromName(Privilege.JCR_READ_ACCESS_CONTROL)})) {
+            throw new AccessDeniedException();
+        }
+    }
+
+    private void createAceNode(SetTree operation, NodeState parentState, AccessControlEntry entry) throws RepositoryException {
+        AccessControlEntryImpl ace = (AccessControlEntryImpl) entry;
+        
+        String uuid = null;
+        boolean isAllow = ace.isAllow();
+        Name nodeName = getUniqueNodeName(parentState, (isAllow) ? "allow" : "deny");
+        Name nodeTypeName = (isAllow) ? NT_REP_GRANT_ACE : NT_REP_DENY_ACE;
+        NodeState aceNode = addNode(operation, parentState, nodeName, uuid, nodeTypeName);
+               
+        // add rep:principalName property
+        String valueStr = ace.getPrincipal().getName();
+        QValue value = qvf.create(valueStr, PropertyType.STRING);
+        addProperty(operation, aceNode, N_REP_PRINCIPAL_NAME, PropertyType.STRING, new QValue[] {value});
+
+        // add rep:privileges MvProperty
+        Privilege[] privs = ace.getPrivileges();
+        QValue[] vls = new QValue[privs.length];
+        Name privilegeName = null;
+        try {
+            for (int i = 0; i < privs.length; i++) {
+                privilegeName = npResolver.getQName(privs[i].getName());
+                vls[i] = qvf.create(privilegeName.toString(), PropertyType.NAME);
+            }            
+        } catch (ValueFormatException e) {
+            throw new RepositoryException(e.getMessage());
+        }
+
+        addProperty(operation, aceNode, N_REP_PRIVILEGES, PropertyType.NAME, vls);
+
+        // TODO: add single and mv restrictions
+    }
+    
+    private NodeState getNodeState(String nodePath) throws RepositoryException {
+        return getNodeState(npResolver.getQPath(nodePath));
+    }
+    
+    private NodeState getRootNodeState() throws RepositoryException {
+        return hierarchyManager.getRootEntry().getNodeState();
+    }
+    
+    private NodeState getNodeState(Path qPath) throws RepositoryException {
+        return hierarchyManager.getNodeState(qPath);
+    }
+    
+    private NodeEntry getNodeEntry(NodeState nodeState) throws RepositoryException {
+        return hierarchyManager.getNodeEntry(nodeState.getPath());
+    }
+    
+    private void checkValidNodePath(String absPath) throws PathNotFoundException, RepositoryException {
+        if (absPath != null) {
+            Path qPath = npResolver.getQPath(absPath);
+            if (!qPath.isAbsolute()) {
+                throw new RepositoryException("Absolute path expected. Found: " + absPath);
+            }
+
+            if (hierarchyManager.getNodeEntry(qPath).getNodeState() == null) {
+                throw new PathNotFoundException(absPath);
+            }
+        }
+    }
+
+    private void checkValidPolicy(AccessControlPolicy policy) throws AccessControlException {
+        if (policy == null || !(policy instanceof AccessControlListImpl)) {
+            throw new AccessControlException("Policy is not applicable ");
+        }
+    }
+    
+    private NodeState addNode(SetTree treeOperation, NodeState parent, Name nodeName, String uuid, Name nodeTypeName) throws RepositoryException {
+        Operation sp = treeOperation.addChildNode(parent, nodeName, nodeTypeName, uuid);
+        itemStateMgr.execute(sp);
+        return (NodeState) ((AddNode) sp).getAddedStates().get(0);
+    }
+    
+    private void addProperty(SetTree treeOperation, NodeState parent, Name propName, int propType, QValue[] values) throws RepositoryException {
+        QPropertyDefinition definition = definitionProvider.getQPropertyDefinition(parent.getAllNodeTypeNames(), propName, propType);
+
+        Operation ap = treeOperation.addChildProperty(parent, propName, propType, values, definition);
+        itemStateMgr.execute(ap);
+    }
+    
+    private void removeNode(NodeState aclNode) throws RepositoryException {
+        Operation removePolicy = Remove.create(aclNode, REMOVE_POLICY_OPTIONS);
+        itemStateMgr.execute(removePolicy);
+    }
+    
+    private void setMixin(NodeState parent, Name mixinName) throws RepositoryException {
+        if (!isNodeType(parent, mixinName)){
+            Operation sm = SetMixin.create(parent, new Name[]{mixinName});
+            itemStateMgr.execute(sm);
+         } else {
+             log.debug(mixinName.toString()+" is already present on the given node state "+parent.getName().toString());
+         }
+    }
+    
+    // copied from jackrabbit-core ACLEditor
+    /**
+     * Create a unique valid name for the Permission nodes to be save.
+     *
+     * @param node a name for the child is resolved
+     * @param name if missing the {@link #DEFAULT_ACE_NAME} is taken
+     * @return the name
+     * @throws RepositoryException if an error occurs
+     */
+    private Name getUniqueNodeName(NodeState node, String name) throws RepositoryException {
+            
+        try {        
+            NameParser.checkFormat(name);
+        } catch (NameException e) {                        
+            log.debug("Invalid path name for Permission: " + name + ".");       
+        }
+
+        int i = 0;
+        String check = name;
+        Name n = npResolver.getQName(check);
+        while (node.hasChildNodeEntry(n, 1)) {
+            check = name + i;
+            n = npResolver.getQName(check);
+            i++;
+        }
+        return n;
+    }
+}
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/AccessControlProviderImpl.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/AccessControlProviderImpl.java
new file mode 100644
index 0000000..de5cc13
--- /dev/null
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/AccessControlProviderImpl.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi.security.authorization.jackrabbit.acl;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.jcr2spi.ItemManager;
+import org.apache.jackrabbit.jcr2spi.config.RepositoryConfig;
+import org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyManager;
+import org.apache.jackrabbit.jcr2spi.nodetype.ItemDefinitionProvider;
+import org.apache.jackrabbit.jcr2spi.security.authorization.AccessControlProvider;
+import org.apache.jackrabbit.jcr2spi.security.authorization.PrivilegeImpl;
+import org.apache.jackrabbit.jcr2spi.state.UpdatableItemStateManager;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.spi.PrivilegeDefinition;
+import org.apache.jackrabbit.spi.RepositoryService;
+import org.apache.jackrabbit.spi.SessionInfo;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+
+public class AccessControlProviderImpl implements AccessControlProvider {
+
+    private RepositoryService service;
+
+    private Map<Name, Privilege> privileges = new HashMap<Name, Privilege>();
+
+    @Override
+    public void init(RepositoryConfig config) throws RepositoryException {
+        this.service = config.getRepositoryService();
+    }
+
+    @Override
+    public Privilege privilegeFromName(SessionInfo sessionInfo, NamePathResolver resolver, String privilegeName) throws RepositoryException {
+        Name name = resolver.getQName(privilegeName);
+        Privilege priv = getPrivilegeFromName(sessionInfo, resolver, name);
+    	
+         if (priv == null) {
+            throw new AccessControlException("Unknown privilege " + privilegeName);
+        } else {
+            return priv;
+        }
+    }
+
+    @Override
+    public Map<String, Privilege> getSupportedPrivileges(SessionInfo sessionInfo, NodeId nodeId, NamePathResolver npResolver) throws RepositoryException {
+        PrivilegeDefinition[] pDefs = service.getSupportedPrivileges(sessionInfo, nodeId);
+        Map<String, Privilege> privilegeMap = new HashMap<String, Privilege>(pDefs.length);
+        for (PrivilegeDefinition def : pDefs) {
+            Privilege p = new PrivilegeImpl(def, pDefs, npResolver);
+            privilegeMap.put(p.getName(), p);
+        }
+        return privilegeMap;
+    }
+
+    @Override
+    public Set<Privilege> getPrivileges(SessionInfo sessionInfo, NodeId id, NamePathResolver npResolver) throws RepositoryException {
+        Name[] privNames = service.getPrivilegeNames(sessionInfo, id);
+        Set<Privilege> pvs = new HashSet<Privilege>(privNames.length);
+        for (Name name : privNames) {
+            Privilege priv = getPrivilegeFromName(sessionInfo, npResolver, name);
+            if (priv != null) {
+                pvs.add(priv);
+            }
+        }
+        return pvs;
+    }
+
+    @Override
+    public AccessControlManager createAccessControlManager(
+            SessionInfo sessionInfo, 
+            UpdatableItemStateManager itemStateManager,
+            ItemManager itemManager,
+            ItemDefinitionProvider definitionProvider,
+            HierarchyManager hierarchyManager, NamePathResolver npResolver) throws RepositoryException {
+        return new AccessControlManagerImpl(sessionInfo, itemStateManager, definitionProvider, hierarchyManager, npResolver, service.getQValueFactory(), this);
+    }
+
+    //--------------------------------------------------------------------------
+
+    private void readPrivilegesFromService(SessionInfo sessionInfo, NamePathResolver resolver) throws RepositoryException {
+        PrivilegeDefinition[] defs = service.getPrivilegeDefinitions(sessionInfo);
+        for (PrivilegeDefinition d : defs) {
+            privileges.put(d.getName(), new PrivilegeImpl(d, defs, resolver));
+        }
+    }
+
+    private Privilege getPrivilegeFromName(SessionInfo sessionInfo, NamePathResolver resolver, Name privilegeName) throws RepositoryException {
+        Privilege priv = privileges.get(privilegeName);
+        if (priv == null) {
+            readPrivilegesFromService(sessionInfo, resolver);
+            if (privileges.containsKey(privilegeName)) {
+                priv = privileges.get(privilegeName);
+            }
+        }
+        return priv;
+    }
+}
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemState.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemState.java
index 9b80f57..6eb635b 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemState.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemState.java
@@ -207,7 +207,12 @@ public abstract class ItemState {
      * @throws RepositoryException
      */
     public NodeState getParent() throws ItemNotFoundException, RepositoryException {
-        return getHierarchyEntry().getParent().getNodeState();
+        // safeguard against root node's null parent
+        NodeEntry parent = getHierarchyEntry().getParent();
+        if (parent != null) {
+            return getHierarchyEntry().getParent().getNodeState();
+        }
+        return null;
     }
 
     /**
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateValidator.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateValidator.java
index db2024e..6bcdd68 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateValidator.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemStateValidator.java
@@ -454,7 +454,7 @@ public class ItemStateValidator {
     /**
      * Verifies that the item represented by the given state is checked-out;
      * throws a <code>VersionException</code> if that's not the case.
-     * <p/>
+     * <p>
      * A node is considered <i>checked-out</i> if it is versionable and
      * checked-out, or is non-versionable but its nearest versionable ancestor
      * is checked-out, or is non-versionable and there are no versionable
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java
index 7bf3112..5b17016 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/SessionItemStateManager.java
@@ -45,12 +45,14 @@ import org.apache.jackrabbit.jcr2spi.nodetype.EffectiveNodeTypeProvider;
 import org.apache.jackrabbit.jcr2spi.nodetype.ItemDefinitionProvider;
 import org.apache.jackrabbit.jcr2spi.operation.AddNode;
 import org.apache.jackrabbit.jcr2spi.operation.AddProperty;
+import org.apache.jackrabbit.jcr2spi.operation.IgnoreOperation;
 import org.apache.jackrabbit.jcr2spi.operation.Move;
 import org.apache.jackrabbit.jcr2spi.operation.Operation;
 import org.apache.jackrabbit.jcr2spi.operation.OperationVisitor;
 import org.apache.jackrabbit.jcr2spi.operation.Remove;
 import org.apache.jackrabbit.jcr2spi.operation.ReorderNodes;
 import org.apache.jackrabbit.jcr2spi.operation.SetMixin;
+import org.apache.jackrabbit.jcr2spi.operation.SetTree;
 import org.apache.jackrabbit.jcr2spi.operation.SetPrimaryType;
 import org.apache.jackrabbit.jcr2spi.operation.SetPropertyValue;
 import org.apache.jackrabbit.jcr2spi.operation.TransientOperationVisitor;
@@ -245,7 +247,9 @@ public class SessionItemStateManager extends TransientOperationVisitor implement
         List<ItemState> newStates = addNodeState(parent, operation.getNodeName(), operation.getNodeTypeName(), operation.getUuid(), def, operation.getOptions());
         operation.addedState(newStates);
 
-        transientStateMgr.addOperation(operation);
+        if (!(operation instanceof IgnoreOperation)) {
+            transientStateMgr.addOperation(operation);
+        }
     }
 
     /**
@@ -265,6 +269,15 @@ public class SessionItemStateManager extends TransientOperationVisitor implement
 
         addPropertyState(parent, propertyName, targetType, operation.getValues(), pDef, operation.getOptions());
 
+        if (!(operation instanceof IgnoreOperation)) {
+            transientStateMgr.addOperation(operation);
+        }
+    }
+
+    /**
+     * @see OperationVisitor#visit(org.apache.jackrabbit.jcr2spi.operation.SetTree)
+     */
+    public void visit(SetTree operation) throws RepositoryException {
         transientStateMgr.addOperation(operation);
     }
 
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/DocViewImportHandler.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/DocViewImportHandler.java
index 27dc6c5..62cc23d 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/DocViewImportHandler.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/DocViewImportHandler.java
@@ -160,7 +160,7 @@ class DocViewImportHandler extends TargetImportHandler {
 
     /**
      * {@inheritDoc}
-     * <p/>
+     * <p>
      * See also {@link org.apache.jackrabbit.commons.xml.DocumentViewExporter#exportProperty(String, String, int, javax.jcr.Value[])}
      * regarding special handling of multi-valued properties on export.
      */
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/ImportHandler.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/ImportHandler.java
index fa6c5f7..e82ab04 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/ImportHandler.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/ImportHandler.java
@@ -43,12 +43,12 @@ import org.xml.sax.helpers.NamespaceSupport;
  * data in System View XML or Document View XML. Processing of the XML is
  * handled by specialized <code>ContentHandler</code>s
  * (i.e. <code>SysViewImportHandler</code> and <code>DocViewImportHandler</code>).
- * <p/>
+ * <p>
  * The actual task of importing though is delegated to the implementation of
  * the <code>{@link Importer}</code> interface.
- * <p/>
+ * <p>
  * <b>Important Note:</b>
- * <p/>
+ * <p>
  * These SAX Event Handlers expect that Namespace URI's and local names are
  * reported in the <code>start/endElement</code> events and that
  * <code>start/endPrefixMapping</code> events are reported
diff --git a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/TargetImportHandler.java b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/TargetImportHandler.java
index 1d374ce..5e1d64b 100644
--- a/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/TargetImportHandler.java
+++ b/jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/xml/TargetImportHandler.java
@@ -109,7 +109,7 @@ abstract class TargetImportHandler extends DefaultHandler {
     /**
      * <code>AppendableValue</code> represents a serialized value that is
      * appendable.
-     * <p/>
+     * <p>
      * <b>Important:</b> Note that in order to free resources
      * <code>{@link #dispose()}</code> should be called as soon as an
      * <code>AppendableValue</code> object is not used anymore.
@@ -188,7 +188,7 @@ abstract class TargetImportHandler extends DefaultHandler {
      * <code>BufferedStringValue</code> represents an appendable
      * serialized value that is either buffered in-memory or backed
      * by a temporary file if its size exceeds a certain limit.
-     * <p/>
+     * <p>
      * <b>Important:</b> Note that in order to free resources
      * <code>{@link #dispose()}</code> should be called as soon as
      * <code>BufferedStringValue</code> instance is not used anymore.
@@ -323,7 +323,7 @@ abstract class TargetImportHandler extends DefaultHandler {
                 } else {
                     if (bufferPos + length > buffer.length) {
                         // reallocate new buffer and spool old buffer contents
-                        char[] newBuffer = new char[buffer.length + BUFFER_INCREMENT];
+                        char[] newBuffer = new char[ bufferPos + length + BUFFER_INCREMENT];
                         System.arraycopy(buffer, 0, newBuffer, 0, bufferPos);
                         buffer = newBuffer;
                     }
diff --git a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/AbstractJCR2SPITest.java b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/AbstractJCR2SPITest.java
index 5ec06b1..c8832cd 100644
--- a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/AbstractJCR2SPITest.java
+++ b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/AbstractJCR2SPITest.java
@@ -54,6 +54,7 @@ import org.apache.jackrabbit.spi.NodeId;
 import org.apache.jackrabbit.spi.NodeInfo;
 import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.PathFactory;
+import org.apache.jackrabbit.spi.PrivilegeDefinition;
 import org.apache.jackrabbit.spi.PropertyId;
 import org.apache.jackrabbit.spi.PropertyInfo;
 import org.apache.jackrabbit.spi.QNodeDefinition;
@@ -66,6 +67,7 @@ import org.apache.jackrabbit.spi.RepositoryService;
 import org.apache.jackrabbit.spi.SessionInfo;
 import org.apache.jackrabbit.spi.Subscription;
 import org.apache.jackrabbit.spi.Path.Element;
+import org.apache.jackrabbit.spi.Tree;
 import org.apache.jackrabbit.spi.commons.AbstractReadableRepositoryService;
 import org.apache.jackrabbit.spi.commons.ItemInfoBuilder;
 import org.apache.jackrabbit.spi.commons.ItemInfoBuilder.NodeInfoBuilder;
@@ -167,6 +169,21 @@ public abstract class AbstractJCR2SPITest extends TestCase implements Repository
             }
 
             @Override
+            public PrivilegeDefinition[] getPrivilegeDefinitions(SessionInfo sessionInfo) throws RepositoryException {
+                return AbstractJCR2SPITest.this.getPrivilegeDefinitions(sessionInfo);
+            }
+
+            @Override
+            public PrivilegeDefinition[] getSupportedPrivileges(SessionInfo sessionInfo, NodeId nodeId) throws RepositoryException {
+                return AbstractJCR2SPITest.this.getSupportedPrivileges(sessionInfo, nodeId);
+            }
+
+            @Override
+            public Name[] getPrivilegeNames(SessionInfo sessionInfo, NodeId nodeId) throws RepositoryException {
+                return AbstractJCR2SPITest.this.getPrivilegeNames(sessionInfo, nodeId);
+            }
+            
+            @Override
             public NodeInfo getNodeInfo(SessionInfo sessionInfo, NodeId nodeId) throws ItemNotFoundException,
                     RepositoryException {
 
@@ -264,6 +281,20 @@ public abstract class AbstractJCR2SPITest extends TestCase implements Repository
         return repositoryService.getItemInfoCache(sessionInfo);
     }
 
+    public PrivilegeDefinition[] getPrivilegeDefinitions(
+            SessionInfo sessionInfo) throws RepositoryException {
+        return repositoryService.getPrivilegeDefinitions(sessionInfo);
+    }
+
+    public PrivilegeDefinition[] getSupportedPrivileges(
+            SessionInfo sessionInfo, NodeId nodeId) throws RepositoryException {
+        return repositoryService.getSupportedPrivileges(sessionInfo, nodeId);
+    }
+    
+    public Name[] getPrivilegeNames(
+            SessionInfo sessionInfo, NodeId nodeId) throws RepositoryException {
+        return repositoryService.getPrivilegeNames(sessionInfo, nodeId);
+    }
     //-----------------------------------< SessionInfo creation and release >---
 
     public SessionInfo obtain(Credentials credentials, String workspaceName) throws RepositoryException {
@@ -338,6 +369,10 @@ public abstract class AbstractJCR2SPITest extends TestCase implements Repository
         repositoryService.submit(batch);
     }
 
+    @Override
+    public Tree createTree(SessionInfo sessionInfo, Batch batch, Name nodeName, Name primaryTypeName, String uniqueId) throws RepositoryException {
+        return repositoryService.createTree(sessionInfo, batch, nodeName, primaryTypeName, uniqueId);
+    }
 
     //-------------------------------------------------------------< Import >---
 
diff --git a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/AbstractRepositoryConfig.java b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/AbstractRepositoryConfig.java
index af2474f..764b766 100644
--- a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/AbstractRepositoryConfig.java
+++ b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/AbstractRepositoryConfig.java
@@ -44,6 +44,11 @@ public abstract class AbstractRepositoryConfig implements RepositoryConfig {
         return DEFAULT_POLL_TIMEOUT;
     }
 
+    @Override
+    public <T> T getConfiguration(String name, T defaultValue) {
+        return null;
+    }
+
     public int getInfoCacheSize() {
         return DEFAULT_INFO_CACHE_SIZE;
     }
diff --git a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderMixedTest.java b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderMixedTest.java
index 9208e14..b9135e6 100644
--- a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderMixedTest.java
+++ b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderMixedTest.java
@@ -1,45 +1,45 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.jcr2spi;
-
-import javax.jcr.ItemExistsException;
-import javax.jcr.RepositoryException;
-import javax.jcr.lock.LockException;
-import javax.jcr.nodetype.ConstraintViolationException;
-import javax.jcr.nodetype.NoSuchNodeTypeException;
-import javax.jcr.version.VersionException;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * <code>ReorderMixedTest</code>...
- */
-public class ReorderMixedTest extends ReorderTest {
-
-    private static Logger log = LoggerFactory.getLogger(ReorderMixedTest.class);
-
-    @Override
-    protected void createOrderableChildren() throws RepositoryException, LockException, ConstraintViolationException, NoSuchNodeTypeException, ItemExistsException, VersionException {
-        child1 = testRootNode.addNode(nodeName2, testNodeType);
-        child2 = testRootNode.addNode(nodeName4, testNodeType);
-        child3 = testRootNode.addNode(nodeName2, testNodeType);
-        child4 = testRootNode.addNode(nodeName2, testNodeType);
-
-        testRootNode.save();
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi;
+
+import javax.jcr.ItemExistsException;
+import javax.jcr.RepositoryException;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.version.VersionException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>ReorderMixedTest</code>...
+ */
+public class ReorderMixedTest extends ReorderTest {
+
+    private static Logger log = LoggerFactory.getLogger(ReorderMixedTest.class);
+
+    @Override
+    protected void createOrderableChildren() throws RepositoryException, LockException, ConstraintViolationException, NoSuchNodeTypeException, ItemExistsException, VersionException {
+        child1 = testRootNode.addNode(nodeName2, testNodeType);
+        child2 = testRootNode.addNode(nodeName4, testNodeType);
+        child3 = testRootNode.addNode(nodeName2, testNodeType);
+        child4 = testRootNode.addNode(nodeName2, testNodeType);
+
+        testRootNode.save();
+    }
+}
diff --git a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderMoveTest.java b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderMoveTest.java
index 8ea6cc4..5cef01f 100644
--- a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderMoveTest.java
+++ b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderMoveTest.java
@@ -1,257 +1,257 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.jcr2spi;
-
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-
-import org.apache.jackrabbit.spi.Path;
-import org.apache.jackrabbit.test.AbstractJCRTest;
-import org.apache.jackrabbit.test.NotExecutableException;
-import org.apache.jackrabbit.util.Text;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * <code>ReorderMoveTest</code> testing various combinations of move/rename
- * and reorder with and without intermediate save, revert and other transient
- * modifications.
- */
-public class ReorderMoveTest extends AbstractJCRTest {
-
-    private static Logger log = LoggerFactory.getLogger(ReorderMoveTest.class);
-
-    private Node destParent;
-    private Node srcParent;
-    private String destPath;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        if (!testRootNode.getPrimaryNodeType().hasOrderableChildNodes()) {
-            throw new NotExecutableException("Test node does not have orderable children.");
-        }
-
-        // create move-destination
-        destParent = testRootNode.addNode(nodeName4, testNodeType);
-        srcParent = testRootNode.addNode(nodeName2, testNodeType);
-
-        destPath = destParent.getPath() + "/" + nodeName3;
-        testRootNode.save();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        destParent = null;
-        srcParent = null;
-        super.tearDown();
-    }
-
-   private Node[] createOrderableChildren(boolean sns) throws RepositoryException {
-        String[] childNames;
-        if (sns) {
-            childNames = new String[] {nodeName2, nodeName2, nodeName2, nodeName2};
-        } else {
-            childNames = new String[] {nodeName1, nodeName2, nodeName3, nodeName4};
-        }
-        Node[] children = new Node[4];
-        children[0] = srcParent.addNode(childNames[0], testNodeType);
-        children[1] = srcParent.addNode(childNames[1], testNodeType);
-        children[2] = srcParent.addNode(childNames[2], testNodeType);
-        children[3] = srcParent.addNode(childNames[3], testNodeType);
-
-        testRootNode.save();
-        return children;
-   }
-
-    private static String getRelPath(Node child) throws RepositoryException {
-        if (child == null) {
-            return null;
-        }
-        String path = child.getPath();
-        return path.substring(path.lastIndexOf('/')+1);
-    }
-
-    private static void testOrder(Node parent, Node[] children) throws RepositoryException {
-        NodeIterator it = parent.getNodes();
-        int i = 0;
-        while (it.hasNext()) {
-            Node child = it.nextNode();
-            assertTrue(child.isSame(children[i]));
-            i++;
-        }
-    }
-
-    /**
-     * Move a orderable child node and reorder the remaining nodes.
-     */
-    public void testMoveAndReorder() throws RepositoryException {
-        Node[] children = createOrderableChildren(false);
-        String oldName = children[2].getName();
-        // move
-        testRootNode.getSession().move(children[2].getPath(), destPath);
-        // reorder
-        srcParent.orderBefore(getRelPath(children[1]), null);
-        testOrder(srcParent, new Node[] {children[0], children[3], children[1]});
-
-        testRootNode.save();
-        testOrder(srcParent, new Node[] {children[0], children[3], children[1]});
-        assertFalse(srcParent.hasNode(oldName));
-    }
-
-    /**
-     * Move a orderable SNS-node and reorder the remaining nodes at source-parent.
-     */
-    public void testMoveAndReorderSNS() throws RepositoryException {
-        Node[] children = createOrderableChildren(true);
-        String snsName = children[0].getName();
-
-        // move
-        testRootNode.getSession().move(children[2].getPath(), destPath);
-        testRootNode.getSession().move(children[1].getPath(), destPath);
-
-        // reorder
-        srcParent.orderBefore(getRelPath(children[0]), null);
-        testOrder(srcParent, new Node[] {children[3], children[0]});
-        assertTrue(srcParent.hasNode(snsName+"[1]"));
-        assertTrue(srcParent.hasNode(snsName+"[2]"));
-        assertFalse(srcParent.hasNode(snsName+"[3]"));
-        assertFalse(srcParent.hasNode(snsName+"[4]"));
-        assertFalse(srcParent.hasNode(snsName+"[5]"));
-
-        testRootNode.save();
-        testOrder(srcParent, new Node[] {children[3], children[0]});
-        assertTrue(srcParent.hasNode(snsName+"[1]"));
-        assertTrue(srcParent.hasNode(snsName+"[2]"));
-        assertFalse(srcParent.hasNode(snsName+"[3]"));
-        assertFalse(srcParent.hasNode(snsName+"[4]"));
-        assertFalse(srcParent.hasNode(snsName+"[5]"));
-
-        // check if move have been successful
-        assertEquals(children[2].getPath(), destPath);
-        assertTrue(children[2].getIndex() == Path.INDEX_DEFAULT);
-        assertEquals(children[1].getPath(), destPath+"[2]");
-    }
-
-    /**
-     * Reorder nodes and move one of the reordered siblings
-     * away. Test the ordering of the remaining siblings.
-     */
-    public void testReorderAndMove() throws RepositoryException {
-        Node[] children = createOrderableChildren(false);
-
-        // reorder first
-        srcParent.orderBefore(getRelPath(children[0]), null);
-        srcParent.orderBefore(getRelPath(children[3]), getRelPath(children[1]));
-        // move
-        testRootNode.getSession().move(children[3].getPath(), destPath);
-
-        testOrder(srcParent, new Node[] {children[1], children[2], children[0]});
-
-        testRootNode.save();
-        testOrder(srcParent, new Node[] {children[1], children[2], children[0]});
-    }
-
-    /**
-     * Reorder same-name-sibling nodes and move one of the reordered siblings
-     * away. Test the ordering of the remaining siblings.
-     */
-    public void testReorderAndMoveSNS() throws RepositoryException {
-        Node[] children = createOrderableChildren(true);
-
-        // reorder first
-        srcParent.orderBefore(getRelPath(children[0]), null);
-        srcParent.orderBefore(getRelPath(children[3]), getRelPath(children[1]));
-        // move
-        testRootNode.getSession().move(children[3].getPath(), destPath);
-
-        testOrder(srcParent, new Node[] {children[1], children[2], children[0]});
-
-        testRootNode.save();
-        testOrder(srcParent, new Node[] {children[1], children[2], children[0]});
-    }
-
-    /**
-     * Any attempt reorder a moved node at its original position must fail.
-     */
-    public void testReorderMovedNode() throws RepositoryException {
-        Node[] children = createOrderableChildren(false);
-
-        String relPath = getRelPath(children[2]);
-        testRootNode.getSession().move(children[2].getPath(), destPath);
-
-        try {
-            srcParent.orderBefore(relPath, null);
-            fail("Reordering a child node that has been moved away must fail.");
-        } catch (ItemNotFoundException e) {
-            // ok
-        }
-    }
-
-    /**
-     * Move a SNS-node and reorder its original siblings afterwards.
-     * Test if reverting the changes results in the original ordering and
-     * hierarchy.
-     */
-    public void testRevertMoveAndReorderSNS() throws RepositoryException {
-        Node[] children = createOrderableChildren(true);
-        // move then reorder
-        testRootNode.getSession().move(children[2].getPath(), destPath);
-        srcParent.orderBefore(getRelPath(children[1]), null);
-        srcParent.orderBefore(getRelPath(children[3]), getRelPath(children[0]));
-
-        testRootNode.refresh(false);
-        testOrder(srcParent, new Node[] {children[0], children[1], children[2], children[3]});
-        assertFalse(destParent.hasNode(Text.getName(destPath)));
-    }
-
-    /**
-     * Move a SNS-node, that got its siblings reordered before.
-     * Test if reverting the changes results in the original ordering and
-     * hierarchy.
-     */
-    public void testRevertReorderAndMoveSNS() throws RepositoryException {
-        Node[] children = createOrderableChildren(true);
-        // reorder then move
-        srcParent.orderBefore(getRelPath(children[1]), null);
-        srcParent.orderBefore(getRelPath(children[3]), getRelPath(children[2]));
-        srcParent.getSession().move(children[2].getPath(), destPath);
-
-        testRootNode.refresh(false);
-        testOrder(srcParent, new Node[] {children[0], children[1], children[2], children[3]});
-        assertFalse(destParent.hasNode(Text.getName(destPath)));
-    }
-
-    /**
-     * Move a SNS-node, that has been reordered before.
-     * Test if reverting the changes results in the original ordering and
-     * hierarchy.
-     */
-    public void testRevertMoveReorderedSNS() throws RepositoryException {
-        Node[] children = createOrderableChildren(true);
-        // reorder then move
-        srcParent.orderBefore(getRelPath(children[1]), null);
-        srcParent.orderBefore(getRelPath(children[3]), getRelPath(children[2]));
-        srcParent.getSession().move(children[1].getPath(), destPath);
-
-        testRootNode.refresh(false);
-        testOrder(srcParent, new Node[] {children[0], children[1], children[2], children[3]});
-        assertFalse(destParent.hasNode(Text.getName(destPath)));
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi;
+
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.spi.Path;
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.jackrabbit.test.NotExecutableException;
+import org.apache.jackrabbit.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>ReorderMoveTest</code> testing various combinations of move/rename
+ * and reorder with and without intermediate save, revert and other transient
+ * modifications.
+ */
+public class ReorderMoveTest extends AbstractJCRTest {
+
+    private static Logger log = LoggerFactory.getLogger(ReorderMoveTest.class);
+
+    private Node destParent;
+    private Node srcParent;
+    private String destPath;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (!testRootNode.getPrimaryNodeType().hasOrderableChildNodes()) {
+            throw new NotExecutableException("Test node does not have orderable children.");
+        }
+
+        // create move-destination
+        destParent = testRootNode.addNode(nodeName4, testNodeType);
+        srcParent = testRootNode.addNode(nodeName2, testNodeType);
+
+        destPath = destParent.getPath() + "/" + nodeName3;
+        testRootNode.save();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        destParent = null;
+        srcParent = null;
+        super.tearDown();
+    }
+
+   private Node[] createOrderableChildren(boolean sns) throws RepositoryException {
+        String[] childNames;
+        if (sns) {
+            childNames = new String[] {nodeName2, nodeName2, nodeName2, nodeName2};
+        } else {
+            childNames = new String[] {nodeName1, nodeName2, nodeName3, nodeName4};
+        }
+        Node[] children = new Node[4];
+        children[0] = srcParent.addNode(childNames[0], testNodeType);
+        children[1] = srcParent.addNode(childNames[1], testNodeType);
+        children[2] = srcParent.addNode(childNames[2], testNodeType);
+        children[3] = srcParent.addNode(childNames[3], testNodeType);
+
+        testRootNode.save();
+        return children;
+   }
+
+    private static String getRelPath(Node child) throws RepositoryException {
+        if (child == null) {
+            return null;
+        }
+        String path = child.getPath();
+        return path.substring(path.lastIndexOf('/')+1);
+    }
+
+    private static void testOrder(Node parent, Node[] children) throws RepositoryException {
+        NodeIterator it = parent.getNodes();
+        int i = 0;
+        while (it.hasNext()) {
+            Node child = it.nextNode();
+            assertTrue(child.isSame(children[i]));
+            i++;
+        }
+    }
+
+    /**
+     * Move a orderable child node and reorder the remaining nodes.
+     */
+    public void testMoveAndReorder() throws RepositoryException {
+        Node[] children = createOrderableChildren(false);
+        String oldName = children[2].getName();
+        // move
+        testRootNode.getSession().move(children[2].getPath(), destPath);
+        // reorder
+        srcParent.orderBefore(getRelPath(children[1]), null);
+        testOrder(srcParent, new Node[] {children[0], children[3], children[1]});
+
+        testRootNode.save();
+        testOrder(srcParent, new Node[] {children[0], children[3], children[1]});
+        assertFalse(srcParent.hasNode(oldName));
+    }
+
+    /**
+     * Move a orderable SNS-node and reorder the remaining nodes at source-parent.
+     */
+    public void testMoveAndReorderSNS() throws RepositoryException {
+        Node[] children = createOrderableChildren(true);
+        String snsName = children[0].getName();
+
+        // move
+        testRootNode.getSession().move(children[2].getPath(), destPath);
+        testRootNode.getSession().move(children[1].getPath(), destPath);
+
+        // reorder
+        srcParent.orderBefore(getRelPath(children[0]), null);
+        testOrder(srcParent, new Node[] {children[3], children[0]});
+        assertTrue(srcParent.hasNode(snsName+"[1]"));
+        assertTrue(srcParent.hasNode(snsName+"[2]"));
+        assertFalse(srcParent.hasNode(snsName+"[3]"));
+        assertFalse(srcParent.hasNode(snsName+"[4]"));
+        assertFalse(srcParent.hasNode(snsName+"[5]"));
+
+        testRootNode.save();
+        testOrder(srcParent, new Node[] {children[3], children[0]});
+        assertTrue(srcParent.hasNode(snsName+"[1]"));
+        assertTrue(srcParent.hasNode(snsName+"[2]"));
+        assertFalse(srcParent.hasNode(snsName+"[3]"));
+        assertFalse(srcParent.hasNode(snsName+"[4]"));
+        assertFalse(srcParent.hasNode(snsName+"[5]"));
+
+        // check if move have been successful
+        assertEquals(children[2].getPath(), destPath);
+        assertTrue(children[2].getIndex() == Path.INDEX_DEFAULT);
+        assertEquals(children[1].getPath(), destPath+"[2]");
+    }
+
+    /**
+     * Reorder nodes and move one of the reordered siblings
+     * away. Test the ordering of the remaining siblings.
+     */
+    public void testReorderAndMove() throws RepositoryException {
+        Node[] children = createOrderableChildren(false);
+
+        // reorder first
+        srcParent.orderBefore(getRelPath(children[0]), null);
+        srcParent.orderBefore(getRelPath(children[3]), getRelPath(children[1]));
+        // move
+        testRootNode.getSession().move(children[3].getPath(), destPath);
+
+        testOrder(srcParent, new Node[] {children[1], children[2], children[0]});
+
+        testRootNode.save();
+        testOrder(srcParent, new Node[] {children[1], children[2], children[0]});
+    }
+
+    /**
+     * Reorder same-name-sibling nodes and move one of the reordered siblings
+     * away. Test the ordering of the remaining siblings.
+     */
+    public void testReorderAndMoveSNS() throws RepositoryException {
+        Node[] children = createOrderableChildren(true);
+
+        // reorder first
+        srcParent.orderBefore(getRelPath(children[0]), null);
+        srcParent.orderBefore(getRelPath(children[3]), getRelPath(children[1]));
+        // move
+        testRootNode.getSession().move(children[3].getPath(), destPath);
+
+        testOrder(srcParent, new Node[] {children[1], children[2], children[0]});
+
+        testRootNode.save();
+        testOrder(srcParent, new Node[] {children[1], children[2], children[0]});
+    }
+
+    /**
+     * Any attempt reorder a moved node at its original position must fail.
+     */
+    public void testReorderMovedNode() throws RepositoryException {
+        Node[] children = createOrderableChildren(false);
+
+        String relPath = getRelPath(children[2]);
+        testRootNode.getSession().move(children[2].getPath(), destPath);
+
+        try {
+            srcParent.orderBefore(relPath, null);
+            fail("Reordering a child node that has been moved away must fail.");
+        } catch (ItemNotFoundException e) {
+            // ok
+        }
+    }
+
+    /**
+     * Move a SNS-node and reorder its original siblings afterwards.
+     * Test if reverting the changes results in the original ordering and
+     * hierarchy.
+     */
+    public void testRevertMoveAndReorderSNS() throws RepositoryException {
+        Node[] children = createOrderableChildren(true);
+        // move then reorder
+        testRootNode.getSession().move(children[2].getPath(), destPath);
+        srcParent.orderBefore(getRelPath(children[1]), null);
+        srcParent.orderBefore(getRelPath(children[3]), getRelPath(children[0]));
+
+        testRootNode.refresh(false);
+        testOrder(srcParent, new Node[] {children[0], children[1], children[2], children[3]});
+        assertFalse(destParent.hasNode(Text.getName(destPath)));
+    }
+
+    /**
+     * Move a SNS-node, that got its siblings reordered before.
+     * Test if reverting the changes results in the original ordering and
+     * hierarchy.
+     */
+    public void testRevertReorderAndMoveSNS() throws RepositoryException {
+        Node[] children = createOrderableChildren(true);
+        // reorder then move
+        srcParent.orderBefore(getRelPath(children[1]), null);
+        srcParent.orderBefore(getRelPath(children[3]), getRelPath(children[2]));
+        srcParent.getSession().move(children[2].getPath(), destPath);
+
+        testRootNode.refresh(false);
+        testOrder(srcParent, new Node[] {children[0], children[1], children[2], children[3]});
+        assertFalse(destParent.hasNode(Text.getName(destPath)));
+    }
+
+    /**
+     * Move a SNS-node, that has been reordered before.
+     * Test if reverting the changes results in the original ordering and
+     * hierarchy.
+     */
+    public void testRevertMoveReorderedSNS() throws RepositoryException {
+        Node[] children = createOrderableChildren(true);
+        // reorder then move
+        srcParent.orderBefore(getRelPath(children[1]), null);
+        srcParent.orderBefore(getRelPath(children[3]), getRelPath(children[2]));
+        srcParent.getSession().move(children[1].getPath(), destPath);
+
+        testRootNode.refresh(false);
+        testOrder(srcParent, new Node[] {children[0], children[1], children[2], children[3]});
+        assertFalse(destParent.hasNode(Text.getName(destPath)));
+    }
+}
diff --git a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderNewAndSavedTest.java b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderNewAndSavedTest.java
index bb4c586..ea6f1e3 100644
--- a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderNewAndSavedTest.java
+++ b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderNewAndSavedTest.java
@@ -1,64 +1,64 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.jcr2spi;
-
-import javax.jcr.ItemExistsException;
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.lock.LockException;
-import javax.jcr.nodetype.ConstraintViolationException;
-import javax.jcr.nodetype.NoSuchNodeTypeException;
-import javax.jcr.version.VersionException;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * <code>ReorderNewAndSavedTest</code>...
- */
-public class ReorderNewAndSavedTest extends ReorderTest {
-
-    private static Logger log = LoggerFactory.getLogger(ReorderNewAndSavedTest.class);
-
-    @Override
-    protected void createOrderableChildren() throws RepositoryException, LockException, ConstraintViolationException, NoSuchNodeTypeException, ItemExistsException, VersionException {
-        child1 = testRootNode.addNode(nodeName1, testNodeType);
-        child2 = testRootNode.addNode(nodeName2, testNodeType);
-        testRootNode.save();
-
-        child3 = testRootNode.addNode(nodeName3, testNodeType);
-        child4 = testRootNode.addNode(nodeName4, testNodeType);
-    }
-
-    @Override
-    public void testRevertReorder() throws RepositoryException {
-        testRootNode.orderBefore(getRelPath(child4), getRelPath(child2));
-        testOrder(testRootNode, new Node[] { child1, child4, child2, child3});
-
-        testRootNode.refresh(false);
-        testOrder(testRootNode, new Node[] { child1, child2 });
-    }
-
-    @Override
-    public void testRevertReorderToEnd() throws RepositoryException {
-        testRootNode.orderBefore(getRelPath(child1), null);
-        testOrder(testRootNode, new Node[] { child2, child3, child4, child1});
-
-        testRootNode.refresh(false);
-        testOrder(testRootNode, new Node[] { child1, child2 });
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi;
+
+import javax.jcr.ItemExistsException;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.version.VersionException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>ReorderNewAndSavedTest</code>...
+ */
+public class ReorderNewAndSavedTest extends ReorderTest {
+
+    private static Logger log = LoggerFactory.getLogger(ReorderNewAndSavedTest.class);
+
+    @Override
+    protected void createOrderableChildren() throws RepositoryException, LockException, ConstraintViolationException, NoSuchNodeTypeException, ItemExistsException, VersionException {
+        child1 = testRootNode.addNode(nodeName1, testNodeType);
+        child2 = testRootNode.addNode(nodeName2, testNodeType);
+        testRootNode.save();
+
+        child3 = testRootNode.addNode(nodeName3, testNodeType);
+        child4 = testRootNode.addNode(nodeName4, testNodeType);
+    }
+
+    @Override
+    public void testRevertReorder() throws RepositoryException {
+        testRootNode.orderBefore(getRelPath(child4), getRelPath(child2));
+        testOrder(testRootNode, new Node[] { child1, child4, child2, child3});
+
+        testRootNode.refresh(false);
+        testOrder(testRootNode, new Node[] { child1, child2 });
+    }
+
+    @Override
+    public void testRevertReorderToEnd() throws RepositoryException {
+        testRootNode.orderBefore(getRelPath(child1), null);
+        testOrder(testRootNode, new Node[] { child2, child3, child4, child1});
+
+        testRootNode.refresh(false);
+        testOrder(testRootNode, new Node[] { child1, child2 });
+    }
+}
diff --git a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderNewSNSTest.java b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderNewSNSTest.java
index f3f58f7..fc38d61 100644
--- a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderNewSNSTest.java
+++ b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderNewSNSTest.java
@@ -1,71 +1,71 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.jcr2spi;
-
-import javax.jcr.ItemExistsException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.lock.LockException;
-import javax.jcr.nodetype.ConstraintViolationException;
-import javax.jcr.nodetype.NoSuchNodeTypeException;
-import javax.jcr.version.VersionException;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * <code>ReorderNewSNSTest</code>...
- */
-public class ReorderNewSNSTest extends ReorderTest {
-
-    private static Logger log = LoggerFactory.getLogger(ReorderNewSNSTest.class);
-
-    @Override
-    protected void createOrderableChildren() throws RepositoryException, LockException, ConstraintViolationException, NoSuchNodeTypeException, ItemExistsException, VersionException {
-        child1 = testRootNode.addNode(nodeName2, testNodeType);
-        child2 = testRootNode.addNode(nodeName2, testNodeType);
-        child3 = testRootNode.addNode(nodeName2, testNodeType);
-        child4 = testRootNode.addNode(nodeName2, testNodeType);
-    }
-
-    @Override
-    public void testRevertReorder() throws RepositoryException {
-        testRootNode.orderBefore(getRelPath(child4), getRelPath(child2));
-        testOrder(testRootNode, new Node[] { child1, child4, child2, child3});
-
-        // NEW child nodes -> must be removed upon refresh
-        testRootNode.refresh(false);
-        NodeIterator it = testRootNode.getNodes(nodeName2);
-        if (it.hasNext()) {
-            fail("Reverting creation and reordering of new SNSs must remove the children again.");
-        }
-    }
-
-    @Override
-    public void testRevertReorderToEnd() throws RepositoryException {
-        testRootNode.orderBefore(getRelPath(child1), null);
-        testOrder(testRootNode, new Node[] { child2, child3, child4, child1});
-
-        // NEW child nodes -> must be removed upon refresh
-        testRootNode.refresh(false);
-        NodeIterator it = testRootNode.getNodes(nodeName2);
-        if (it.hasNext()) {
-            fail("Reverting creation and reordering of new SNSs must remove the children again.");
-        }
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi;
+
+import javax.jcr.ItemExistsException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.version.VersionException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>ReorderNewSNSTest</code>...
+ */
+public class ReorderNewSNSTest extends ReorderTest {
+
+    private static Logger log = LoggerFactory.getLogger(ReorderNewSNSTest.class);
+
+    @Override
+    protected void createOrderableChildren() throws RepositoryException, LockException, ConstraintViolationException, NoSuchNodeTypeException, ItemExistsException, VersionException {
+        child1 = testRootNode.addNode(nodeName2, testNodeType);
+        child2 = testRootNode.addNode(nodeName2, testNodeType);
+        child3 = testRootNode.addNode(nodeName2, testNodeType);
+        child4 = testRootNode.addNode(nodeName2, testNodeType);
+    }
+
+    @Override
+    public void testRevertReorder() throws RepositoryException {
+        testRootNode.orderBefore(getRelPath(child4), getRelPath(child2));
+        testOrder(testRootNode, new Node[] { child1, child4, child2, child3});
+
+        // NEW child nodes -> must be removed upon refresh
+        testRootNode.refresh(false);
+        NodeIterator it = testRootNode.getNodes(nodeName2);
+        if (it.hasNext()) {
+            fail("Reverting creation and reordering of new SNSs must remove the children again.");
+        }
+    }
+
+    @Override
+    public void testRevertReorderToEnd() throws RepositoryException {
+        testRootNode.orderBefore(getRelPath(child1), null);
+        testOrder(testRootNode, new Node[] { child2, child3, child4, child1});
+
+        // NEW child nodes -> must be removed upon refresh
+        testRootNode.refresh(false);
+        NodeIterator it = testRootNode.getNodes(nodeName2);
+        if (it.hasNext()) {
+            fail("Reverting creation and reordering of new SNSs must remove the children again.");
+        }
+    }
+}
diff --git a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderNewTest.java b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderNewTest.java
index e50fde8..87ffab6 100644
--- a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderNewTest.java
+++ b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderNewTest.java
@@ -1,71 +1,71 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.jcr2spi;
-
-import javax.jcr.ItemExistsException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.lock.LockException;
-import javax.jcr.nodetype.ConstraintViolationException;
-import javax.jcr.nodetype.NoSuchNodeTypeException;
-import javax.jcr.version.VersionException;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * <code>ReorderNewTest</code>...
- */
-public class ReorderNewTest extends ReorderTest {
-
-    private static Logger log = LoggerFactory.getLogger(ReorderNewTest.class);
-
-    @Override
-    protected void createOrderableChildren() throws RepositoryException, LockException, ConstraintViolationException, NoSuchNodeTypeException, ItemExistsException, VersionException {
-        child1 = testRootNode.addNode(nodeName1, testNodeType);
-        child2 = testRootNode.addNode(nodeName2, testNodeType);
-        child3 = testRootNode.addNode(nodeName3, testNodeType);
-        child4 = testRootNode.addNode(nodeName4, testNodeType);
-    }
-
-    @Override
-    public void testRevertReorder() throws RepositoryException {
-        testRootNode.orderBefore(getRelPath(child4), getRelPath(child2));
-        testOrder(testRootNode, new Node[] { child1, child4, child2, child3});
-
-        // NEW child nodes -> must be removed upon refresh
-        testRootNode.refresh(false);
-        NodeIterator it = testRootNode.getNodes();
-        if (it.hasNext()) {
-            fail("Reverting creation and reordering of new children must remove the children again.");
-        }
-    }
-
-    @Override
-    public void testRevertReorderToEnd() throws RepositoryException {
-        testRootNode.orderBefore(getRelPath(child1), null);
-        testOrder(testRootNode, new Node[] { child2, child3, child4, child1});
-
-        // NEW child nodes -> must be removed upon refresh
-        testRootNode.refresh(false);
-        NodeIterator it = testRootNode.getNodes();
-        if (it.hasNext()) {
-            fail("Reverting creation and reordering of new children must remove the children again.");
-        }
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi;
+
+import javax.jcr.ItemExistsException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.version.VersionException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>ReorderNewTest</code>...
+ */
+public class ReorderNewTest extends ReorderTest {
+
+    private static Logger log = LoggerFactory.getLogger(ReorderNewTest.class);
+
+    @Override
+    protected void createOrderableChildren() throws RepositoryException, LockException, ConstraintViolationException, NoSuchNodeTypeException, ItemExistsException, VersionException {
+        child1 = testRootNode.addNode(nodeName1, testNodeType);
+        child2 = testRootNode.addNode(nodeName2, testNodeType);
+        child3 = testRootNode.addNode(nodeName3, testNodeType);
+        child4 = testRootNode.addNode(nodeName4, testNodeType);
+    }
+
+    @Override
+    public void testRevertReorder() throws RepositoryException {
+        testRootNode.orderBefore(getRelPath(child4), getRelPath(child2));
+        testOrder(testRootNode, new Node[] { child1, child4, child2, child3});
+
+        // NEW child nodes -> must be removed upon refresh
+        testRootNode.refresh(false);
+        NodeIterator it = testRootNode.getNodes();
+        if (it.hasNext()) {
+            fail("Reverting creation and reordering of new children must remove the children again.");
+        }
+    }
+
+    @Override
+    public void testRevertReorderToEnd() throws RepositoryException {
+        testRootNode.orderBefore(getRelPath(child1), null);
+        testOrder(testRootNode, new Node[] { child2, child3, child4, child1});
+
+        // NEW child nodes -> must be removed upon refresh
+        testRootNode.refresh(false);
+        NodeIterator it = testRootNode.getNodes();
+        if (it.hasNext()) {
+            fail("Reverting creation and reordering of new children must remove the children again.");
+        }
+    }
+}
diff --git a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderReferenceableSNSTest.java b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderReferenceableSNSTest.java
index d0b2944..2b8dc22 100644
--- a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderReferenceableSNSTest.java
+++ b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderReferenceableSNSTest.java
@@ -1,49 +1,49 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.jcr2spi;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-
-import org.apache.jackrabbit.test.NotExecutableException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * <code>ReorderSNSTest</code>...
- */
-public class ReorderReferenceableSNSTest extends ReorderTest {
-
-    private static Logger log = LoggerFactory.getLogger(ReorderReferenceableSNSTest.class);
-
-    @Override
-    protected void createOrderableChildren() throws RepositoryException, NotExecutableException {
-        child1 = testRootNode.addNode(nodeName2, testNodeType);
-        child2 = testRootNode.addNode(nodeName2, testNodeType);
-        child3 = testRootNode.addNode(nodeName2, testNodeType);
-        child4 = testRootNode.addNode(nodeName2, testNodeType);
-        Node[] children = new Node[] { child1, child2, child3, child4};
-        for (int i = 0; i < children.length; i++) {
-            if (children[i].canAddMixin(mixReferenceable)) {
-                children[i].addMixin(mixReferenceable);
-            } else {
-                throw new NotExecutableException();
-            }
-        }
-        testRootNode.save();
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.test.NotExecutableException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>ReorderSNSTest</code>...
+ */
+public class ReorderReferenceableSNSTest extends ReorderTest {
+
+    private static Logger log = LoggerFactory.getLogger(ReorderReferenceableSNSTest.class);
+
+    @Override
+    protected void createOrderableChildren() throws RepositoryException, NotExecutableException {
+        child1 = testRootNode.addNode(nodeName2, testNodeType);
+        child2 = testRootNode.addNode(nodeName2, testNodeType);
+        child3 = testRootNode.addNode(nodeName2, testNodeType);
+        child4 = testRootNode.addNode(nodeName2, testNodeType);
+        Node[] children = new Node[] { child1, child2, child3, child4};
+        for (int i = 0; i < children.length; i++) {
+            if (children[i].canAddMixin(mixReferenceable)) {
+                children[i].addMixin(mixReferenceable);
+            } else {
+                throw new NotExecutableException();
+            }
+        }
+        testRootNode.save();
+    }
+}
diff --git a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderSNSTest.java b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderSNSTest.java
index e0d85bb..5c3acf4 100644
--- a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderSNSTest.java
+++ b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderSNSTest.java
@@ -1,73 +1,73 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.jcr2spi;
-
-import javax.jcr.Item;
-import javax.jcr.ItemExistsException;
-import javax.jcr.RepositoryException;
-import javax.jcr.lock.LockException;
-import javax.jcr.nodetype.ConstraintViolationException;
-import javax.jcr.nodetype.NoSuchNodeTypeException;
-import javax.jcr.version.VersionException;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * <code>ReorderSNSTest</code>...
- */
-public class ReorderSNSTest extends ReorderTest {
-
-    private static Logger log = LoggerFactory.getLogger(ReorderSNSTest.class);
-
-    @Override
-    protected void createOrderableChildren() throws RepositoryException, LockException, ConstraintViolationException, NoSuchNodeTypeException, ItemExistsException, VersionException {
-        child1 = testRootNode.addNode(nodeName2, testNodeType);
-        child2 = testRootNode.addNode(nodeName2, testNodeType);
-        child3 = testRootNode.addNode(nodeName2, testNodeType);
-        child4 = testRootNode.addNode(nodeName2, testNodeType);
-
-        testRootNode.save();
-    }
-
-    public void testIndexAfterReorder() throws RepositoryException {
-        testRootNode.orderBefore(getRelPath(child1), getRelPath(child3));
-        assertTrue(child1.getIndex() == 2);
-        assertTrue(child2.getIndex() == 1);
-        assertTrue(child3.getIndex() == 3);
-        assertTrue(child4.getIndex() == 4);
-
-        testRootNode.save();
-        assertTrue(child1.getIndex() == 2);
-        assertTrue(child2.getIndex() == 1);
-        assertTrue(child3.getIndex() == 3);
-        assertTrue(child4.getIndex() == 4);
-    }
-
-    public void testReorder3() throws RepositoryException {
-        String pathBefore = child3.getPath();
-
-        testRootNode.orderBefore(getRelPath(child3), getRelPath(child1));
-        testRootNode.save();
-
-        Item itemIndex3 = testRootNode.getSession().getItem(pathBefore);
-        assertTrue(itemIndex3.isSame(child2));
-
-        Item item3 = testRootNode.getSession().getItem(child3.getPath());
-        assertTrue(item3.isSame(child3));
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi;
+
+import javax.jcr.Item;
+import javax.jcr.ItemExistsException;
+import javax.jcr.RepositoryException;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.version.VersionException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>ReorderSNSTest</code>...
+ */
+public class ReorderSNSTest extends ReorderTest {
+
+    private static Logger log = LoggerFactory.getLogger(ReorderSNSTest.class);
+
+    @Override
+    protected void createOrderableChildren() throws RepositoryException, LockException, ConstraintViolationException, NoSuchNodeTypeException, ItemExistsException, VersionException {
+        child1 = testRootNode.addNode(nodeName2, testNodeType);
+        child2 = testRootNode.addNode(nodeName2, testNodeType);
+        child3 = testRootNode.addNode(nodeName2, testNodeType);
+        child4 = testRootNode.addNode(nodeName2, testNodeType);
+
+        testRootNode.save();
+    }
+
+    public void testIndexAfterReorder() throws RepositoryException {
+        testRootNode.orderBefore(getRelPath(child1), getRelPath(child3));
+        assertTrue(child1.getIndex() == 2);
+        assertTrue(child2.getIndex() == 1);
+        assertTrue(child3.getIndex() == 3);
+        assertTrue(child4.getIndex() == 4);
+
+        testRootNode.save();
+        assertTrue(child1.getIndex() == 2);
+        assertTrue(child2.getIndex() == 1);
+        assertTrue(child3.getIndex() == 3);
+        assertTrue(child4.getIndex() == 4);
+    }
+
+    public void testReorder3() throws RepositoryException {
+        String pathBefore = child3.getPath();
+
+        testRootNode.orderBefore(getRelPath(child3), getRelPath(child1));
+        testRootNode.save();
+
+        Item itemIndex3 = testRootNode.getSession().getItem(pathBefore);
+        assertTrue(itemIndex3.isSame(child2));
+
+        Item item3 = testRootNode.getSession().getItem(child3.getPath());
+        assertTrue(item3.isSame(child3));
+    }
+}
diff --git a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderTest.java b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderTest.java
index 327e485..163b2dc 100644
--- a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderTest.java
+++ b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/ReorderTest.java
@@ -1,166 +1,166 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.jcr2spi;
-
-import javax.jcr.ItemExistsException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.UnsupportedRepositoryOperationException;
-import javax.jcr.lock.LockException;
-import javax.jcr.nodetype.ConstraintViolationException;
-import javax.jcr.nodetype.NoSuchNodeTypeException;
-import javax.jcr.version.VersionException;
-
-import org.apache.jackrabbit.test.AbstractJCRTest;
-import org.apache.jackrabbit.test.NotExecutableException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * <code>ReorderTest</code>...
- */
-public class ReorderTest extends AbstractJCRTest {
-
-    private static Logger log = LoggerFactory.getLogger(ReorderTest.class);
-
-    protected Node child1;
-    protected Node child2;
-    protected Node child3;
-    protected Node child4;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        if (!testRootNode.getPrimaryNodeType().hasOrderableChildNodes()) {
-            throw new NotExecutableException("Test node does not have orderable children.");
-        }
-        NodeIterator it = testRootNode.getNodes();
-        if (it.hasNext()) {
-            throw new NotExecutableException("Test node already contains child nodes");
-        }
-        createOrderableChildren();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        child1 = null;
-        child2 = null;
-        child3 = null;
-        child4 = null;
-        super.tearDown();
-    }
-
-    protected void createOrderableChildren() throws RepositoryException, LockException, ConstraintViolationException, NoSuchNodeTypeException, ItemExistsException, VersionException, NotExecutableException {
-        child1 = testRootNode.addNode(nodeName1, testNodeType);
-        child2 = testRootNode.addNode(nodeName2, testNodeType);
-        child3 = testRootNode.addNode(nodeName3, testNodeType);
-        child4 = testRootNode.addNode(nodeName4, testNodeType);
-
-        testRootNode.save();
-    }
-
-    protected static String getRelPath(Node child) throws RepositoryException {
-        if (child == null) {
-            return null;
-        }
-        String path = child.getPath();
-        return path.substring(path.lastIndexOf('/')+1);
-    }
-
-    protected static void testOrder(Node parent, Node[] children) throws RepositoryException {
-        NodeIterator it = parent.getNodes();
-        int i = 0;
-        while (it.hasNext()) {
-            Node child = it.nextNode();
-            if (i >= children.length) {
-                fail("Reorder added a child node.");
-            }
-            assertTrue("Wrong order of children: " + child + " is not the same as " + children[i], child.isSame(children[i]));
-            i++;
-        }
-
-        if (i < children.length-1) {
-            fail("Reorder removed a child node.");
-        }
-    }
-
-    public void testReorder() throws RepositoryException {
-        testRootNode.orderBefore(getRelPath(child1), getRelPath(child3));
-        testOrder(testRootNode, new Node[] { child2, child1, child3, child4});
-
-        testRootNode.save();
-        testOrder(testRootNode, new Node[] { child2, child1, child3, child4});
-    }
-
-    public void testReorderToEnd() throws RepositoryException, ConstraintViolationException, UnsupportedRepositoryOperationException, VersionException {
-        testRootNode.orderBefore(getRelPath(child2), null);
-        testOrder(testRootNode, new Node[] { child1, child3, child4, child2});
-
-        testRootNode.save();
-        testOrder(testRootNode, new Node[] { child1, child3, child4, child2});
-    }
-
-    public void testRevertReorder() throws RepositoryException {
-        testRootNode.orderBefore(getRelPath(child4), getRelPath(child2));
-        testOrder(testRootNode, new Node[] { child1, child4, child2, child3});
-
-        testRootNode.refresh(false);
-        testOrder(testRootNode, new Node[] { child1, child2, child3, child4});
-    }
-
-    public void testRevertReorderToEnd() throws RepositoryException {
-        testRootNode.orderBefore(getRelPath(child1), null);
-        testOrder(testRootNode, new Node[] { child2, child3, child4, child1});
-
-        testRootNode.refresh(false);
-        testOrder(testRootNode, new Node[] { child1, child2, child3, child4});
-    }
-
-    public void testReorder2() throws RepositoryException {
-        testRootNode.orderBefore(getRelPath(child3), getRelPath(child1));
-        testRootNode.save();
-
-        Session otherSession = getHelper().getReadOnlySession();
-        try {
-            testOrder((Node) otherSession.getItem(testRootNode.getPath()), new Node[] {child3, child1, child2, child4});
-        } finally {
-            otherSession.logout();
-        }
-    }
-
-    public void testReorderTwice() throws RepositoryException {
-        testRootNode.orderBefore(getRelPath(child2), null);
-        testRootNode.orderBefore(getRelPath(child4), getRelPath(child1));
-
-        testOrder(testRootNode, new Node[] { child4, child1, child3, child2});
-        testRootNode.save();
-        testOrder(testRootNode, new Node[] { child4, child1, child3, child2});
-    }
-
-    public void testReorderFinallyOriginalOrder() throws RepositoryException {
-        testRootNode.orderBefore(getRelPath(child4), getRelPath(child1));
-        testRootNode.orderBefore(getRelPath(child3), getRelPath(child4));
-        testRootNode.orderBefore(getRelPath(child2), getRelPath(child3));
-        testRootNode.orderBefore(getRelPath(child1), getRelPath(child2));
-
-        testOrder(testRootNode, new Node[] { child1, child2, child3, child4});
-        testRootNode.save();
-        testOrder(testRootNode, new Node[] { child1, child2, child3, child4});
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi;
+
+import javax.jcr.ItemExistsException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.lock.LockException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NoSuchNodeTypeException;
+import javax.jcr.version.VersionException;
+
+import org.apache.jackrabbit.test.AbstractJCRTest;
+import org.apache.jackrabbit.test.NotExecutableException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>ReorderTest</code>...
+ */
+public class ReorderTest extends AbstractJCRTest {
+
+    private static Logger log = LoggerFactory.getLogger(ReorderTest.class);
+
+    protected Node child1;
+    protected Node child2;
+    protected Node child3;
+    protected Node child4;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (!testRootNode.getPrimaryNodeType().hasOrderableChildNodes()) {
+            throw new NotExecutableException("Test node does not have orderable children.");
+        }
+        NodeIterator it = testRootNode.getNodes();
+        if (it.hasNext()) {
+            throw new NotExecutableException("Test node already contains child nodes");
+        }
+        createOrderableChildren();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        child1 = null;
+        child2 = null;
+        child3 = null;
+        child4 = null;
+        super.tearDown();
+    }
+
+    protected void createOrderableChildren() throws RepositoryException, LockException, ConstraintViolationException, NoSuchNodeTypeException, ItemExistsException, VersionException, NotExecutableException {
+        child1 = testRootNode.addNode(nodeName1, testNodeType);
+        child2 = testRootNode.addNode(nodeName2, testNodeType);
+        child3 = testRootNode.addNode(nodeName3, testNodeType);
+        child4 = testRootNode.addNode(nodeName4, testNodeType);
+
+        testRootNode.save();
+    }
+
+    protected static String getRelPath(Node child) throws RepositoryException {
+        if (child == null) {
+            return null;
+        }
+        String path = child.getPath();
+        return path.substring(path.lastIndexOf('/')+1);
+    }
+
+    protected static void testOrder(Node parent, Node[] children) throws RepositoryException {
+        NodeIterator it = parent.getNodes();
+        int i = 0;
+        while (it.hasNext()) {
+            Node child = it.nextNode();
+            if (i >= children.length) {
+                fail("Reorder added a child node.");
+            }
+            assertTrue("Wrong order of children: " + child + " is not the same as " + children[i], child.isSame(children[i]));
+            i++;
+        }
+
+        if (i < children.length-1) {
+            fail("Reorder removed a child node.");
+        }
+    }
+
+    public void testReorder() throws RepositoryException {
+        testRootNode.orderBefore(getRelPath(child1), getRelPath(child3));
+        testOrder(testRootNode, new Node[] { child2, child1, child3, child4});
+
+        testRootNode.save();
+        testOrder(testRootNode, new Node[] { child2, child1, child3, child4});
+    }
+
+    public void testReorderToEnd() throws RepositoryException, ConstraintViolationException, UnsupportedRepositoryOperationException, VersionException {
+        testRootNode.orderBefore(getRelPath(child2), null);
+        testOrder(testRootNode, new Node[] { child1, child3, child4, child2});
+
+        testRootNode.save();
+        testOrder(testRootNode, new Node[] { child1, child3, child4, child2});
+    }
+
+    public void testRevertReorder() throws RepositoryException {
+        testRootNode.orderBefore(getRelPath(child4), getRelPath(child2));
+        testOrder(testRootNode, new Node[] { child1, child4, child2, child3});
+
+        testRootNode.refresh(false);
+        testOrder(testRootNode, new Node[] { child1, child2, child3, child4});
+    }
+
+    public void testRevertReorderToEnd() throws RepositoryException {
+        testRootNode.orderBefore(getRelPath(child1), null);
+        testOrder(testRootNode, new Node[] { child2, child3, child4, child1});
+
+        testRootNode.refresh(false);
+        testOrder(testRootNode, new Node[] { child1, child2, child3, child4});
+    }
+
+    public void testReorder2() throws RepositoryException {
+        testRootNode.orderBefore(getRelPath(child3), getRelPath(child1));
+        testRootNode.save();
+
+        Session otherSession = getHelper().getReadOnlySession();
+        try {
+            testOrder((Node) otherSession.getItem(testRootNode.getPath()), new Node[] {child3, child1, child2, child4});
+        } finally {
+            otherSession.logout();
+        }
+    }
+
+    public void testReorderTwice() throws RepositoryException {
+        testRootNode.orderBefore(getRelPath(child2), null);
+        testRootNode.orderBefore(getRelPath(child4), getRelPath(child1));
+
+        testOrder(testRootNode, new Node[] { child4, child1, child3, child2});
+        testRootNode.save();
+        testOrder(testRootNode, new Node[] { child4, child1, child3, child2});
+    }
+
+    public void testReorderFinallyOriginalOrder() throws RepositoryException {
+        testRootNode.orderBefore(getRelPath(child4), getRelPath(child1));
+        testRootNode.orderBefore(getRelPath(child3), getRelPath(child4));
+        testRootNode.orderBefore(getRelPath(child2), getRelPath(child3));
+        testRootNode.orderBefore(getRelPath(child1), getRelPath(child2));
+
+        testOrder(testRootNode, new Node[] { child1, child2, child3, child4});
+        testRootNode.save();
+        testOrder(testRootNode, new Node[] { child1, child2, child3, child4});
+    }
+}
diff --git a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/lock/OpenScopedLockTest.java b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/lock/OpenScopedLockTest.java
index 8761286..9ca784c 100644
--- a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/lock/OpenScopedLockTest.java
+++ b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/lock/OpenScopedLockTest.java
@@ -18,8 +18,11 @@ package org.apache.jackrabbit.jcr2spi.lock;
 
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
+import javax.jcr.Session;
 import javax.jcr.lock.Lock;
 import javax.jcr.lock.LockException;
+import javax.jcr.lock.LockManager;
+import javax.jcr.nodetype.NodeType;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -89,7 +92,10 @@ public class OpenScopedLockTest extends AbstractLockTest {
         String lockToken = lock.getLockToken();
         try {
             superuser.removeLockToken(lockToken);
-            assertNull("After token transfer lock-token must not be visible", lock.getLockToken());
+
+            String nlt = lock.getLockToken();
+            assertTrue("freshly obtained lock token must either be null or the same as the one returned earlier",
+                    nlt == null || nlt.equals(lockToken));
         } finally {
             // move lock token back in order to have lock removed properly
             superuser.addLockToken(lockToken);
@@ -213,4 +219,64 @@ public class OpenScopedLockTest extends AbstractLockTest {
             throw e;
         }
     }
+
+    public void testIsLockedWhileAnotherLockIsPresent() throws Exception {
+
+        Session s = lockedNode.getSession();
+        LockManager lm = s.getWorkspace().getLockManager();
+
+        String l2token = null;
+        String l2path = null;
+
+        String path = lockedNode.getPath();
+        String lockToken = lock.getLockToken();
+        assertTrue(lm.isLocked(path));
+        assertTrue(lm.holdsLock(path));
+        lm.removeLockToken(lockToken);
+
+        Session anotherSession = null;
+        try {
+            // check lock is seen by new session
+            anotherSession = getHelper().getSuperuserSession();
+            LockManager anotherLockManager = anotherSession.getWorkspace().getLockManager();
+            assertTrue(anotherLockManager.isLocked(path));
+            assertTrue(anotherLockManager.holdsLock(path));
+
+            // create a second lock
+            Node l2node = anotherSession.getNode(path).getParent().addNode("second-lock");
+            l2node.addMixin(NodeType.MIX_LOCKABLE);
+            anotherSession.save();
+            l2path = l2node.getPath();
+
+            Lock l2 = anotherLockManager.lock(l2path, false, false, Long.MAX_VALUE, "foobar");
+            l2token = l2.getLockToken();
+            assertNotNull(l2token);
+            anotherSession.save();
+
+            anotherSession.refresh(false);
+            assertTrue(anotherLockManager.isLocked(path));
+            assertTrue(anotherLockManager.holdsLock(path));
+
+            // try to unlock the lock obtained from the other session
+            anotherLockManager.addLockToken(lockToken);
+            anotherLockManager.unlock(path);
+            anotherSession.save();
+
+            // unlock "my" lock
+            anotherLockManager.unlock(l2path);
+            anotherSession.save();
+            l2path = null;
+        }
+        finally {
+            if (anotherSession != null) {
+                anotherSession.logout();
+            }
+            if (l2path != null && l2token != null) {
+                superuser.refresh(false);
+                LockManager sulm = superuser.getWorkspace().getLockManager();
+                sulm.addLockToken(l2token);
+                sulm.unlock(l2path);
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/security/Jcr2SpiSecurityTestSuite.java b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/security/Jcr2SpiSecurityTestSuite.java
new file mode 100644
index 0000000..2847fa3
--- /dev/null
+++ b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/security/Jcr2SpiSecurityTestSuite.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi.security;
+
+import junit.framework.TestSuite;
+
+/**
+ * <code>Jcr2SpiTestSuite</code>...
+ */
+public class Jcr2SpiSecurityTestSuite extends TestSuite {
+
+    public Jcr2SpiSecurityTestSuite() {
+        super("JCR2SPI Security tests");
+
+        // all jcr2spi security tests
+        addTest(org.apache.jackrabbit.jcr2spi.security.authorization.jackrabbit.acl.TestAll.suite());
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/AccessControlListImplTest.java b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/AccessControlListImplTest.java
new file mode 100644
index 0000000..9c6a978
--- /dev/null
+++ b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/AccessControlListImplTest.java
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi.security.authorization.jackrabbit.acl;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.security.AccessControlEntry;
+import javax.jcr.security.AccessControlList;
+import javax.jcr.security.Privilege;
+
+import junit.framework.Assert;
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.QValueFactory;
+import org.apache.jackrabbit.spi.commons.conversion.DefaultNamePathResolver;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+import org.apache.jackrabbit.spi.commons.value.QValueFactoryImpl;
+import org.apache.jackrabbit.test.api.security.AbstractAccessControlTest;
+
+/**
+ * Tests the functionality of the JCR AccessControlList API implementation. The
+ * purpose is to test the consistency of the access control list by a, adding ,
+ * deleting and modifying entries in the list.
+ */
+public class AccessControlListImplTest extends AbstractAccessControlTest {
+
+    private QValueFactory vFactory;
+
+    private Principal unknownPrincipal;
+    private Principal knownPrincipal;
+
+    private NamePathResolver resolver;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        resolver = new DefaultNamePathResolver(superuser);
+        vFactory = QValueFactoryImpl.getInstance();
+
+        unknownPrincipal = getHelper().getUnknownPrincipal(superuser);
+        knownPrincipal = new Principal() {
+            @Override
+            public String getName() {
+                return "everyone";
+            }
+        };
+    }
+
+    private JackrabbitAccessControlList createAccessControList(String aclPath)
+            throws RepositoryException {
+        return new AccessControlListImpl(aclPath, resolver, vFactory);
+    }
+
+    private Map<String, Value> createEmptyRestriction() {
+        return Collections.<String, Value> emptyMap();
+    }
+
+    public void testAddingDifferentEntries() throws Exception {
+        JackrabbitAccessControlList acl = createAccessControList(testRoot);
+
+        // allow read to unknownPrincipal
+        Privilege[] p = privilegesFromName(Privilege.JCR_READ);
+        acl.addAccessControlEntry(unknownPrincipal, p);
+
+        // allow addChildNodes to secondPrincipal
+        p = privilegesFromName(Privilege.JCR_ADD_CHILD_NODES);
+        acl.addAccessControlEntry(knownPrincipal, p);
+
+        // deny modifyAccessControl to 'unknown' principal
+        p = privilegesFromName(Privilege.JCR_MODIFY_ACCESS_CONTROL);
+        acl.addEntry(unknownPrincipal, p, false);
+
+        // deny jcr:nodeTypeManagement to secondPrincipal
+        p = privilegesFromName(Privilege.JCR_NODE_TYPE_MANAGEMENT);
+        acl.addEntry(knownPrincipal, p, false);
+
+        // four different entries
+        Assert.assertEquals(4, acl.size());
+        
+        // UnknownPrincipal entries
+        AccessControlEntry[] pentries = getEntries(acl, unknownPrincipal);
+        Assert.assertEquals(2, pentries.length);
+        
+        // secondPrincipal entries
+        AccessControlEntry[] sentries = getEntries(acl, knownPrincipal);
+        Assert.assertEquals(2, sentries.length);
+        
+    }
+
+    public void testMultipleEntryEffect() throws Exception {
+        JackrabbitAccessControlList acl = createAccessControList(testRoot);
+        Privilege[] privileges = privilegesFromName(Privilege.JCR_READ);
+
+        // GRANT 'read' privilege to the Admin user -> list now contains one
+        // allow entry
+        assertTrue(acl.addAccessControlEntry(unknownPrincipal, privileges));
+
+        // policy contains a single entry
+        assertEquals(1, acl.size());
+
+        AccessControlEntry[] entries = acl.getAccessControlEntries();
+
+        // ... and the entry grants a single privilege
+        assertEquals(1, entries[0].getPrivileges().length);
+        assertEquals("jcr:read", entries[0].getPrivileges()[0].getName());
+
+        // GRANT 'add_child_node' privilege for the admin user -> same entry but
+        // with an additional 'add_child_node' privilege.
+        privileges = privilegesFromNames(new String[] {Privilege.JCR_ADD_CHILD_NODES, Privilege.JCR_READ });
+        assertTrue(acl.addAccessControlEntry(unknownPrincipal, privileges));
+
+        // A new Entry was added -> entries count should be 2.
+        assertEquals(2, acl.size());
+
+        // The single entry should now contain both 'read' and 'add_child_nodes'
+        // privileges for the same principal.
+        assertEquals(1, acl.getAccessControlEntries()[0].getPrivileges().length);
+        assertEquals(2, acl.getAccessControlEntries()[1].getPrivileges().length);
+
+        // adding a privilege that's already granted for the same principal ->
+        // again modified as the client doesn't care about possible compaction the
+        // server may want to make.
+        privileges = privilegesFromNames(new String[] { Privilege.JCR_READ });
+        assertTrue(acl.addAccessControlEntry(unknownPrincipal, privileges));
+        assertEquals(3, acl.size());
+
+        // revoke the read privilege
+        assertTrue("Fail to revoke read privilege", acl.addEntry(unknownPrincipal, privileges, false, createEmptyRestriction()));
+
+        // should now be 3 entries -> 2 allow entry + a deny entry
+        assertEquals(4, acl.size());
+    }
+
+    public void testMultipleEntryEffect2() throws Exception {
+        JackrabbitAccessControlList acl = createAccessControList(testRoot);
+        // GRANT a read privilege
+        Privilege[] privileges = privilegesFromNames(new String[] { Privilege.JCR_READ });
+        assertTrue("New Entry -> grants read privilege", acl.addAccessControlEntry(unknownPrincipal, privileges));
+
+        assertTrue("Fail to revoke the read privilege", acl.addEntry(unknownPrincipal, privileges, false, createEmptyRestriction()));
+        Assert.assertEquals(2, acl.size());
+    }
+
+    // -------------------------------------------------------< utility methods >---
+
+    private AccessControlEntry[] getEntries(AccessControlList acl, Principal princ) throws RepositoryException {
+        AccessControlEntry[] entries = acl.getAccessControlEntries();
+        List<AccessControlEntry> entriesPerPrincipal = new ArrayList<AccessControlEntry>(2);
+        for (AccessControlEntry entry : entries) {
+            if (entry.getPrincipal().getName().equals(princ.getName())) {
+                entriesPerPrincipal.add(entry);
+            }
+        }
+        return entriesPerPrincipal.toArray(new AccessControlEntry[entriesPerPrincipal.size()]);
+    }
+ }
diff --git a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/AccessControlManagerImplTest.java b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/AccessControlManagerImplTest.java
new file mode 100644
index 0000000..cad4dbb
--- /dev/null
+++ b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/AccessControlManagerImplTest.java
@@ -0,0 +1,213 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi.security.authorization.jackrabbit.acl;
+
+import java.security.Principal;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.security.AccessControlList;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.AccessControlPolicyIterator;
+import javax.jcr.security.Privilege;
+
+import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
+import org.apache.jackrabbit.test.NotExecutableException;
+import org.apache.jackrabbit.test.api.security.AbstractAccessControlTest;
+
+public class AccessControlManagerImplTest extends AbstractAccessControlTest {    
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();        
+    }
+    
+    private Principal getUnknownPrincipal() throws NotExecutableException, RepositoryException {
+        return getHelper().getUnknownPrincipal(superuser);
+    }
+    
+    public void testGetAndHasPrivileges() throws Exception {
+        Privilege[] privileges = acMgr.getPrivileges(testRoot);
+        assertNotNull(privileges);
+        assertTrue(acMgr.hasPrivileges(testRoot, privileges));
+    }
+    
+    /**
+     * Tests the binding state of a policy.
+     * @throws Exception
+     */
+    public void testGetPolicesAfterSetPoliciesCall() throws Exception {
+        try {
+            AccessControlPolicyIterator policies = acMgr.getApplicablePolicies(testRoot);
+            AccessControlPolicy policy = null;
+            while (policies.hasNext()) {
+                policy = policies.nextAccessControlPolicy();
+                acMgr.setPolicy(testRoot, policy);
+                AccessControlPolicy[] acl = acMgr.getPolicies(testRoot);
+                assertNotNull(acl);
+            }
+        } finally {
+            superuser.refresh(false);
+        }        
+    }
+
+    /**
+     * This should be able to return the policies that has been transiently added
+     * to the node at testRoot, as the getPolicies api specifies that the method should
+     * take the transient changes into account.
+     * @throws Exception
+     */
+    public void testRemovePolicyAfterASetPoliciesCall() throws Exception {
+        try {
+            AccessControlPolicyIterator policies = acMgr.getApplicablePolicies(testRoot);
+            while (policies.hasNext()) {
+                AccessControlList acl = (AccessControlListImpl) policies.nextAccessControlPolicy();
+                
+                // GRANT read privilege
+                acl.addAccessControlEntry(getUnknownPrincipal(), privilegesFromName(Privilege.JCR_READ));
+                
+                acMgr.setPolicy(testRoot, acl);
+                
+                AccessControlPolicy[] transientPolicy = acMgr.getPolicies(testRoot);
+                
+                acMgr.removePolicy(testRoot, transientPolicy[0]);
+                
+                assertEquals(0, acMgr.getPolicies(testRoot).length);
+            }
+
+        } finally {
+            superuser.refresh(false);
+        }
+    }
+    
+    /**
+     * Test removing an effective policy.
+     */
+    public void testRemovePolicyAfterASaveCall() throws Exception {
+        try {
+            AccessControlList[] acl = (AccessControlList[]) acMgr.getPolicies(testRoot);
+            if (acl.length > 0) {
+                acMgr.removePolicy(testRoot, acl[0]);
+            } else {                
+                AccessControlPolicy policy = acMgr.getApplicablePolicies(testRoot).nextAccessControlPolicy();
+                acMgr.setPolicy(testRoot, policy);
+                acMgr.removePolicy(testRoot, policy);
+            }
+
+            // transient removal           
+            AccessControlPolicy[] noPolicies = acMgr.getPolicies(testRoot);            
+            assertEquals(0, noPolicies.length);
+
+            // save changes -> removal of protected items on jcr-server
+            superuser.save();
+        } catch (Exception e) {
+            throw new RepositoryException(e.getMessage());
+        } finally {
+            superuser.refresh(false);
+        }
+    }
+    
+    /**
+     * JCR mandates that the path specified for getPrivileges method must
+     * be absolute and points to an existing node.
+     * @throws Exception
+     */
+    public void testGetPrivilegesOnNonExistingNode() throws Exception {
+        try {
+            acMgr.getPrivileges(getPathToNonExistingNode());
+            fail("Must throw a PathNotFoundException");
+        } catch (PathNotFoundException e) {
+            // success
+        }        
+    }
+
+    /**
+     * Add an AccessControlList with four entries. This will result in having the result in:
+     * Transient-space: An ACL node that has four child-nodes.
+     * Persistent-state: An ACL node that has one child-node.
+     * NOTE: That Jackrabbit-core tries to internally merge the entries that belongs to the same 
+     * principal, which is not the case for the client-side ACM implementation.
+     */
+    public void testAddingFourAccessControlEntries() throws Exception {
+        try {
+            AccessControlList acl = (AccessControlList) getACL(testRoot);        
+            
+            acl.addAccessControlEntry(getUnknownPrincipal(), privilegesFromName(Privilege.JCR_READ));
+            acl.addAccessControlEntry(getUnknownPrincipal(), privilegesFromName(Privilege.JCR_READ));
+            acl.addAccessControlEntry(getUnknownPrincipal(), privilegesFromName(Privilege.JCR_READ));
+            acl.addAccessControlEntry(getUnknownPrincipal(), privilegesFromName(Privilege.JCR_READ));
+            
+            acMgr.setPolicy(testRoot, acl);
+
+            // Transient-space: Must contain FOUR ace nodes.
+            assertEquals(4, testRootNode.getNode("rep:policy").getNodes().getSize());
+            
+            superuser.save();
+            
+            // Persistent-state: Must contain a single ace node -> entries were merged
+            assertEquals(1, testRootNode.getNode("rep:policy").getNodes().getSize());
+        } finally {
+            superuser.refresh(false);
+        }
+    }
+    
+    /**
+     * Test retrieving a policy after a save call.
+     * @throws Exception
+     */
+    public void testGetPoliciesAfterASaveCall() throws Exception {
+        try {
+            JackrabbitAccessControlList policy = (JackrabbitAccessControlList) getACL(testRoot);
+
+            String aclPath = policy.getPath();
+            assertEquals(aclPath, testRoot);
+
+            // GRANT 'read' privilege to principal
+            policy.addAccessControlEntry(getUnknownPrincipal(), privilegesFromName(Privilege.JCR_READ));
+
+            // GRANT 'add_child_nodes' privilege
+            policy.addAccessControlEntry(getUnknownPrincipal(), privilegesFromName(Privilege.JCR_ADD_CHILD_NODES));
+
+            // bind the policy and save changes
+            acMgr.setPolicy(testRoot, policy);
+            superuser.save();
+
+            Node aclNode = testRootNode.getNode("rep:policy");
+            assertNotNull(aclNode);
+            
+            NodeIterator nit = aclNode.getNodes();
+            
+            // Jackrabbit-core will merge the two entries -> only a single aceNode will be created.
+            assertEquals(1, nit.getSize());            
+        } finally {
+            superuser.refresh(false);
+        }
+    }
+    
+    private AccessControlPolicy getACL(String absPath) throws RepositoryException {
+        AccessControlList acl = null;
+        if (acMgr.getPolicies(absPath).length > 0) {
+            acl = (AccessControlList) acMgr.getPolicies(absPath)[0];
+        } else {
+            acl = (AccessControlList) acMgr.getApplicablePolicies(absPath).nextAccessControlPolicy();
+        }
+        return acl;
+    }
+
+}
diff --git a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/TestAll.java b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/TestAll.java
new file mode 100644
index 0000000..05d05f9
--- /dev/null
+++ b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/security/authorization/jackrabbit/acl/TestAll.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi.security.authorization.jackrabbit.acl;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+public class TestAll {
+
+    public static Test suite() {
+
+        TestSuite suite = new TestSuite("jcr2spi jackrabbit security tests");
+
+        suite.addTestSuite(AccessControlListImplTest.class);
+        suite.addTestSuite(AccessControlManagerImplTest.class);
+
+        return suite;
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/version/LabelTest.java b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/version/LabelTest.java
index e92256a..5986792 100644
--- a/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/version/LabelTest.java
+++ b/jackrabbit-jcr2spi/src/test/java/org/apache/jackrabbit/jcr2spi/version/LabelTest.java
@@ -1,77 +1,77 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.jcr2spi.version;
-
-import java.util.Arrays;
-import java.util.List;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.version.Version;
-
-import org.apache.jackrabbit.test.api.version.VersionLabelTest;
-
-public class LabelTest extends VersionLabelTest {
-
-    public void testRemovedLabel2() throws RepositoryException {
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel, false);
-        vHistory.removeVersionLabel(versionLabel);
-
-        List<String> labels = Arrays.asList(vHistory.getVersionLabels());
-        assertFalse("VersionHistory.getVersionLabels() must not return a removed label.",labels.contains(versionLabel));
-    }
-
-    public void testRemovedLabel3() throws RepositoryException {
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel, false);
-        vHistory.removeVersionLabel(versionLabel);
-
-        List<String> labels = Arrays.asList(vHistory.getVersionLabels(rootVersion));
-        assertFalse("VersionHistory.getVersionLabels(Version) must not return a removed label.",labels.contains(versionLabel));
-    }
-
-    public void testMoveLabel2() throws RepositoryException {
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel, false);
-
-        versionableNode.checkout();
-        Version v = versionableNode.checkin();
-        vHistory.addVersionLabel(v.getName(), versionLabel, true);
-
-        List<String> labels = Arrays.asList(vHistory.getVersionLabels(v));
-        assertTrue(labels.contains(versionLabel));
-    }
-
-    public void testMoveLabel3() throws RepositoryException {
-        versionableNode.checkout();
-        Version v = versionableNode.checkin();
-
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel, false);
-        vHistory.addVersionLabel(v.getName(), versionLabel, true);
-
-        List<String> labels = Arrays.asList(vHistory.getVersionLabels(rootVersion));
-        assertFalse(labels.contains(versionLabel));
-    }
-
-    public void testMoveLabel4() throws RepositoryException {
-        versionableNode.checkout();
-        Version v = versionableNode.checkin();
-
-        vHistory.addVersionLabel(rootVersion.getName(), versionLabel, false);
-        vHistory.addVersionLabel(v.getName(), versionLabel, true);
-
-        Version v2 = vHistory.getVersionByLabel(versionLabel);
-        assertTrue(v2.isSame(v));
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.jcr2spi.version;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.version.Version;
+
+import org.apache.jackrabbit.test.api.version.VersionLabelTest;
+
+public class LabelTest extends VersionLabelTest {
+
+    public void testRemovedLabel2() throws RepositoryException {
+        vHistory.addVersionLabel(version.getName(), versionLabel, false);
+        vHistory.removeVersionLabel(versionLabel);
+
+        List<String> labels = Arrays.asList(vHistory.getVersionLabels());
+        assertFalse("VersionHistory.getVersionLabels() must not return a removed label.",labels.contains(versionLabel));
+    }
+
+    public void testRemovedLabel3() throws RepositoryException {
+        vHistory.addVersionLabel(version.getName(), versionLabel, false);
+        vHistory.removeVersionLabel(versionLabel);
+
+        List<String> labels = Arrays.asList(vHistory.getVersionLabels(version));
+        assertFalse("VersionHistory.getVersionLabels(Version) must not return a removed label.",labels.contains(versionLabel));
+    }
+
+    public void testMoveLabel2() throws RepositoryException {
+        vHistory.addVersionLabel(version.getName(), versionLabel, false);
+
+        versionableNode.checkout();
+        Version v = versionableNode.checkin();
+        vHistory.addVersionLabel(v.getName(), versionLabel, true);
+
+        List<String> labels = Arrays.asList(vHistory.getVersionLabels(v));
+        assertTrue(labels.contains(versionLabel));
+    }
+
+    public void testMoveLabel3() throws RepositoryException {
+        versionableNode.checkout();
+        Version v = versionableNode.checkin();
+
+        vHistory.addVersionLabel(version.getName(), versionLabel, false);
+        vHistory.addVersionLabel(v.getName(), versionLabel, true);
+
+        List<String> labels = Arrays.asList(vHistory.getVersionLabels(version));
+        assertFalse(labels.contains(versionLabel));
+    }
+
+    public void testMoveLabel4() throws RepositoryException {
+        versionableNode.checkout();
+        Version v = versionableNode.checkin();
+
+        vHistory.addVersionLabel(version.getName(), versionLabel, false);
+        vHistory.addVersionLabel(v.getName(), versionLabel, true);
+
+        Version v2 = vHistory.getVersionByLabel(versionLabel);
+        assertTrue(v2.isSame(v));
+    }
+}
diff --git a/jackrabbit-jcr2spi/src/test/resources/accessControlProvider.properties b/jackrabbit-jcr2spi/src/test/resources/accessControlProvider.properties
new file mode 100644
index 0000000..d89cc0e
--- /dev/null
+++ b/jackrabbit-jcr2spi/src/test/resources/accessControlProvider.properties
@@ -0,0 +1,16 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+org.apache.jackrabbit.jcr2spi.AccessControlProvider.class=org.apache.jackrabbit.jcr2spi.security.authorization.jackrabbit.acl.AccessControlProviderImpl
diff --git a/jackrabbit-parent/pom.xml b/jackrabbit-parent/pom.xml
index 3ee5364..c4ad597 100644
--- a/jackrabbit-parent/pom.xml
+++ b/jackrabbit-parent/pom.xml
@@ -34,7 +34,7 @@
   <groupId>org.apache.jackrabbit</groupId>
   <artifactId>jackrabbit-parent</artifactId>
   <name>Jackrabbit Parent POM</name>
-  <version>2.3.6</version>
+  <version>2.10.1</version>
   <packaging>pom</packaging>
 
   <issueManagement>
@@ -43,12 +43,19 @@
   </issueManagement>
 
   <properties>
-    <slf4j.version>1.6.4</slf4j.version>
-    <logback.version>1.0.0</logback.version>
     <jetty.version>6.1.22</jetty.version>
-    <tika.version>1.0</tika.version>
-    <clirr.baseline.version>2.2.0</clirr.baseline.version>
+    <tika.version>1.7</tika.version>
     <project.reporting.outputEncoding>${project.build.sourceEncoding}</project.reporting.outputEncoding>
+    <clirr.baseline.version>2.2.0</clirr.baseline.version>
+    <!-- Note that we're using SLF4J API version 1.6 when compiling     -->
+    <!-- core Oak components but more recent SLF4J and Logback versions -->
+    <!-- when compiling and running test cases and the oak-run jar.     -->
+    <!-- This way it's possible to use Oak also in environments that    -->
+    <!-- only provide SLF4J version 1.6, while still using a more       -->
+    <!-- recent version when we are in control the runtime environment. -->
+    <slf4j.api.version>1.6.6</slf4j.api.version>
+    <slf4j.version>1.7.4</slf4j.version> <!-- sync with logback version -->
+    <logback.version>1.0.12</logback.version>
   </properties>
 
   <url>http://jackrabbit.apache.org/</url>
@@ -89,13 +96,10 @@
         </plugins>
       </build>
     </profile>
-    <!-- JCR-661: The antrun tools.jar dependency is not needed on macs -->
     <profile>
-      <id>non-mac</id>
+      <id>jdk-tools</id>
       <activation>
-        <os>
-          <family>!mac</family>
-        </os>
+        <file><exists>${java.home}/../lib/tools.jar</exists></file>
       </activation>
       <build>
         <plugins>
@@ -136,22 +140,22 @@
 
   <build>
     <plugins>
-      <!-- JCR-2087: Upgrade to Java 5 as the base platform -->
+      <!-- JCR-3327: Upgrade to Java SE 6 -->
       <plugin>
         <artifactId>maven-compiler-plugin</artifactId>
         <configuration>
-          <target>1.5</target>
-          <source>1.5</source>
+          <target>1.6</target>
+          <source>1.6</source>
         </configuration>
       </plugin>
       <!-- Generate aggregate Javadocs -->
       <plugin>
         <artifactId>maven-javadoc-plugin</artifactId>
         <configuration>
-          <source>1.5</source>
+          <source>1.6</source>
           <aggregate>true</aggregate>
           <links>
-            <link>http://download.oracle.com/javase/1.5.0/docs/api/</link>
+            <link>http://docs.oracle.com/javase/6/docs/api/</link>
             <link>http://www.day.com/maven/javax.jcr/javadocs/jcr-2.0/</link>
           </links>
         </configuration>
@@ -162,7 +166,7 @@
         <artifactId>maven-idea-plugin</artifactId>
         <configuration>
           <downloadSources>true</downloadSources>
-          <jdkLevel>1.5</jdkLevel>
+          <jdkLevel>1.6</jdkLevel>
         </configuration>
       </plugin>
       <plugin>
@@ -205,7 +209,7 @@
         </plugin>
         <plugin>
           <artifactId>maven-surefire-plugin</artifactId>
-          <version>2.4.3</version>
+          <version>2.18</version>
         </plugin>
         <plugin>
           <artifactId>maven-failsafe-plugin</artifactId>
@@ -272,7 +276,7 @@
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-report-plugin</artifactId>
-        <version>2.4.3</version>
+        <version>2.18</version>
       </plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
@@ -284,7 +288,7 @@
         <artifactId>maven-pmd-plugin</artifactId>
         <version>2.4</version>
         <configuration>
-          <targetJdk>1.5</targetJdk>
+          <targetJdk>1.6</targetJdk>
         </configuration>
       </plugin>
       <plugin>
@@ -325,7 +329,7 @@
       <dependency>
         <groupId>commons-io</groupId>
         <artifactId>commons-io</artifactId>
-        <version>1.4</version>
+        <version>2.2</version>
       </dependency>
       <dependency>
         <groupId>org.apache.geronimo.specs</groupId>
@@ -335,7 +339,7 @@
       <dependency>
         <groupId>org.slf4j</groupId>
         <artifactId>slf4j-api</artifactId>
-        <version>${slf4j.version}</version>
+        <version>${slf4j.api.version}</version>
       </dependency>
       <dependency>
         <groupId>org.slf4j</groupId>
@@ -350,7 +354,7 @@
       <dependency>
         <groupId>commons-httpclient</groupId>
         <artifactId>commons-httpclient</artifactId>
-        <version>3.0</version>
+        <version>3.1</version>
         <!-- JCR-683: Exclude bad transitive dependencies -->
         <exclusions>
           <exclusion>
@@ -362,7 +366,7 @@
       <dependency>
         <groupId>org.apache.lucene</groupId>
         <artifactId>lucene-core</artifactId>
-        <version>3.0.3</version>
+        <version>3.6.0</version>
       </dependency>
       <dependency>
         <groupId>org.apache.tika</groupId>
@@ -902,8 +906,8 @@
   </developers>
 
   <scm>
-    <connection>scm:svn:http://svn.apache.org/repos/asf/maven/pom/tags/2.3.6/jackrabbit-parent</connection>
-    <developerConnection>scm:svn:https://svn.apache.org/repos/asf/maven/pom/tags/2.3.6/jackrabbit-parent</developerConnection>
-    <url>http://svn.apache.org/viewvc/maven/pom/tags/2.3.6/jackrabbit-parent</url>
+    <connection>scm:svn:http://svn.apache.org/repos/asf/maven/pom/tags/jackrabbit-2.10.1/jackrabbit-parent</connection>
+    <developerConnection>scm:svn:https://svn.apache.org/repos/asf/maven/pom/tags/jackrabbit-2.10.1/jackrabbit-parent</developerConnection>
+    <url>http://svn.apache.org/viewvc/maven/pom/tags/jackrabbit-2.10.1/jackrabbit-parent</url>
   </scm>
 </project>
diff --git a/jackrabbit-spi-commons/pom.xml b/jackrabbit-spi-commons/pom.xml
index 5151244..8aed38f 100644
--- a/jackrabbit-spi-commons/pom.xml
+++ b/jackrabbit-spi-commons/pom.xml
@@ -26,7 +26,7 @@
   <parent>
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>jackrabbit-parent</artifactId>
-    <version>2.3.6</version>
+    <version>2.10.1</version>
     <relativePath>../jackrabbit-parent/pom.xml</relativePath>
   </parent>
   <artifactId>jackrabbit-spi-commons</artifactId>
@@ -80,22 +80,18 @@
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-spi</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-commons</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>commons-collections</groupId>
       <artifactId>commons-collections</artifactId>
     </dependency>
     <dependency>
-      <groupId>commons-io</groupId>
-      <artifactId>commons-io</artifactId>
-    </dependency>
-    <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
     </dependency>
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/AbstractRepositoryService.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/AbstractRepositoryService.java
index c9bbdec..9a43766 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/AbstractRepositoryService.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/AbstractRepositoryService.java
@@ -72,6 +72,7 @@ import org.apache.jackrabbit.spi.QueryInfo;
 import org.apache.jackrabbit.spi.RepositoryService;
 import org.apache.jackrabbit.spi.SessionInfo;
 import org.apache.jackrabbit.spi.Subscription;
+import org.apache.jackrabbit.spi.Tree;
 import org.apache.jackrabbit.spi.commons.identifier.IdFactoryImpl;
 import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
 import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
@@ -517,6 +518,14 @@ public abstract class AbstractRepositoryService implements RepositoryService {
     /**
      * @throws UnsupportedRepositoryOperationException always.
      */
+    @Override
+    public Tree createTree(SessionInfo sessionInfo, Batch batch, Name nodeName, Name primaryTypeName, String uniqueId) throws RepositoryException {
+        throw new UnsupportedRepositoryOperationException();
+    }
+
+    /**
+     * @throws UnsupportedRepositoryOperationException always.
+     */
     public void importXml(SessionInfo sessionInfo,
                           NodeId parentId,
                           InputStream xmlStream,
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/EventImpl.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/EventImpl.java
index 347a66b..2baa3c8 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/EventImpl.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/EventImpl.java
@@ -26,6 +26,7 @@ import org.apache.jackrabbit.spi.QValue;
 import javax.jcr.RepositoryException;
 import javax.jcr.UnsupportedRepositoryOperationException;
 import java.io.Serializable;
+import java.util.Arrays;
 import java.util.Map;
 import java.util.Collections;
 import java.util.HashMap;
@@ -193,7 +194,7 @@ public class EventImpl implements Event, Serializable {
             .append("itemId: ").append(itemId).append(", ")
             .append("parentId: ").append(parentId).append(", ")
             .append("primaryNodeTypeName: ").append(primaryNodeTypeName).append(", ")
-            .append("mixinTypeNames: ").append(mixinTypeNames).append(", ")
+            .append("mixinTypeNames: ").append(Arrays.toString(mixinTypeNames)).append(", ")
             .append("userId").append(userId)
             .append("]")
             .toString();
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/SerializableBatch.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/SerializableBatch.java
index 34ff594..57183f2 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/SerializableBatch.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/SerializableBatch.java
@@ -22,6 +22,7 @@ import org.apache.jackrabbit.spi.QValue;
 import org.apache.jackrabbit.spi.PropertyId;
 import org.apache.jackrabbit.spi.ItemId;
 import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.Tree;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.ValueFormatException;
@@ -131,6 +132,10 @@ public class SerializableBatch implements Batch, Serializable {
         recording.add(new Move(srcNodeId, destParentNodeId, destName));
     }
 
+    public void setTree(NodeId parentId, Tree contentTree)
+            throws RepositoryException {
+        recording.add(new SetTree(parentId, contentTree));
+    }
     //----------------------------< internal >----------------------------------
 
     public interface Operation extends Serializable {
@@ -170,6 +175,25 @@ public class SerializableBatch implements Batch, Serializable {
         }
     }
 
+    private static class SetTree implements Operation {
+
+        private final NodeId parentId;
+
+        private final Tree contentTree;
+
+        SetTree(NodeId parentId, Tree contentTree) {
+            this.parentId = parentId;
+            this.contentTree = contentTree;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void replay(Batch batch) throws RepositoryException {
+            batch.setTree(parentId, contentTree);
+        }
+    }
+
     private static class AddProperty implements Operation {
 
         private final NodeId parentId;
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/SessionExtensions.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/SessionExtensions.java
index b0f475f..b7790a2 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/SessionExtensions.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/SessionExtensions.java
@@ -1,32 +1,32 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.spi.commons;
-
-import javax.jcr.Session;
-
-/**
- * Provides additional methods for {@link Session} access..
- */
-public interface SessionExtensions {
-
-	/**
-	 * Sets the specified {@link Session} attribute.
-	 * @param name attribute name
-	 * @param value attribute value
-	 */
-	public void setAttribute(String name, Object value);
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.spi.commons;
+
+import javax.jcr.Session;
+
+/**
+ * Provides additional methods for {@link Session} access..
+ */
+public interface SessionExtensions {
+
+	/**
+	 * Sets the specified {@link Session} attribute.
+	 * @param name attribute name
+	 * @param value attribute value
+	 */
+	public void setAttribute(String name, Object value);
+}
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ChangeLogImpl.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ChangeLogImpl.java
index c79b157..b3362ca 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ChangeLogImpl.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ChangeLogImpl.java
@@ -25,6 +25,7 @@ import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.NodeId;
 import org.apache.jackrabbit.spi.PropertyId;
 import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.spi.Tree;
 
 /**
  * This {@link ChangeLog} implementation simply keeps back all calls to its {@link Batch} methods as
@@ -80,5 +81,9 @@ public class ChangeLogImpl extends AbstractChangeLog<Operation> {
         addOperation(Operations.setValue(propertyId, values));
     }
 
+    @Override
+    public void setTree(NodeId parentId, Tree contentTree) throws RepositoryException {
+        addOperation(Operations.setTree(parentId, contentTree));
+    }
 }
 
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ConsolidatingChangeLog.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ConsolidatingChangeLog.java
index cf8d1ef..1207ba6 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ConsolidatingChangeLog.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/ConsolidatingChangeLog.java
@@ -29,6 +29,7 @@ import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.PathFactory;
 import org.apache.jackrabbit.spi.PropertyId;
 import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.spi.Tree;
 import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
 
 /**
@@ -125,6 +126,11 @@ public class ConsolidatingChangeLog extends AbstractChangeLog<ConsolidatingChang
         addOperation(CancelableOperations.setValue(propertyId, values));
     }
 
+    @Override
+    public void setTree(NodeId parentId, Tree contentTree) throws RepositoryException {
+        addOperation(CancelableOperations.setTree(parentId, contentTree));
+    }
+
     /**
      * Determines the cancellation behavior from the list of {@link ChangeLogImpl#operations operations}
      * and the current operation <code>op</code>:
@@ -772,6 +778,47 @@ public class ConsolidatingChangeLog extends AbstractChangeLog<ConsolidatingChang
             return new SetValue(propertyId, values);
         }
 
+
+        //--------------------------------------------------------< SetTree >---
+        public static class SetTree extends Operations.SetTree implements CancelableOperation {
+
+            public SetTree(NodeId parentId, Tree contentTree) {
+                super(parentId, contentTree);
+            }
+
+            /**
+             * The cancellation only considers canceling the parent node, which corresponds
+             * to the policy node.
+             */
+            public int cancel(CancelableOperation other) throws RepositoryException {
+                if (other instanceof Remove) {
+                    Path thisPath = ConsolidatingChangeLog.getPath(parentId, tree.getName());
+                    Path otherPath = ConsolidatingChangeLog.getPath(((Remove) other).itemId);
+                    if (thisPath == null || otherPath == null) {
+                        return CANCEL_NONE;
+                    }
+                    if (thisPath.equals(otherPath)) {
+                        return CANCEL_BOTH;
+                    }
+                    return (thisPath.isDescendantOf(otherPath))
+                            ? CANCEL_THIS
+                            : CANCEL_NONE;
+                }
+                return CANCEL_NONE;
+            }
+        }
+
+        /**
+         * Factory method for creating an {@link SetTree} operation.
+         * @see Batch#setTree(NodeId, Tree)
+         *
+         * @param parentId
+         * @param tree
+         * @return
+         */
+        public static CancelableOperation setTree(NodeId parentId, Tree tree) {
+            return new SetTree(parentId, tree);
+        }
     }
 
 }
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/Operations.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/Operations.java
index 9d8c09a..f0c8562 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/Operations.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/batch/Operations.java
@@ -26,6 +26,7 @@ import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.NodeId;
 import org.apache.jackrabbit.spi.PropertyId;
 import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.spi.Tree;
 
 /**
  * Factory for creating {@link Operation}s. The inner classes of this class
@@ -229,7 +230,7 @@ public final class Operations {
 
         @Override
         public String toString() {
-            return "AddProperty[" + parentId + ", " + propertyName + ", " + values + "]";
+            return "AddProperty[" + parentId + ", " + propertyName + ", " + Arrays.toString(values) + "]";
         }
 
         @Override
@@ -546,7 +547,7 @@ public final class Operations {
 
         @Override
         public String toString() {
-            return "SetMixins[" + nodeId + ", " + mixinNodeTypeNames + "]";
+            return "SetMixins[" + nodeId + ", " + Arrays.toString(mixinNodeTypeNames) + "]";
         }
 
         @Override
@@ -715,7 +716,7 @@ public final class Operations {
 
         @Override
         public String toString() {
-            return "SetValue[" + propertyId + ", " + values + "]";
+            return "SetValue[" + propertyId + ", " + Arrays.toString(values) + "]";
         }
 
         @Override
@@ -784,4 +785,68 @@ public final class Operations {
             : o.hashCode();
     }
 
+    //--------------------------------------------------------------< SetTree >---
+    public static class SetTree implements Operation {
+        protected final NodeId parentId;
+        protected final Tree tree;
+
+        public SetTree(NodeId parentId, Tree tree) {
+            super();
+            this.parentId = parentId;
+            this.tree = tree;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public void apply(Batch batch) throws RepositoryException {
+            batch.setTree(parentId, tree);
+        }
+
+        //----------------------------< Object >---
+        @Override
+        public String toString() {
+            return "SetTree[" + parentId + ", " + tree+"]";
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (null == other) {
+                return false;
+            }
+            if (this == other) {
+                return true;
+            }
+            if (other instanceof SetTree) {
+                return equals((SetTree) other);
+            }
+            return false;
+        }
+
+        public boolean equals(SetTree other) {
+            return Operations.equals(parentId, other.parentId)
+                && Operations.equals(tree, other.tree);
+        }
+
+        @Override
+        public int hashCode() {
+            return 41 * (
+                          41 + Operations.hashCode(parentId))
+                       + Operations.hashCode(tree);
+        }
+    }
+
+    /**
+     * Factory method for creating an {@link SetTree} operation.
+     * @see Batch#addNode(NodeId, AddItem)
+     *
+     * @param parentId
+     * @param addItem
+     * @param nodetypeName
+     * @param uuid
+     * @return
+     */
+    public static Operation setTree(NodeId parentId, Tree contentTree) {
+        return new SetTree(parentId, contentTree);
+    }
 }
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/CachingNameResolver.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/CachingNameResolver.java
index b56e97a..30e9b87 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/CachingNameResolver.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/CachingNameResolver.java
@@ -90,6 +90,9 @@ public class CachingNameResolver implements NameResolver {
      * @throws NamespaceException if the namespace URI can not be resolved
      */
     public String getJCRName(Name name) throws NamespaceException {
+        if (name.getNamespaceURI().length() == 0) {
+            return name.getLocalName();
+        }
         String jcrName = (String) cache.get(name);
         if (jcrName == null) {
             jcrName = resolver.getJCRName(name);
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/PathParser.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/PathParser.java
index 0041860..0346a29 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/PathParser.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/conversion/PathParser.java
@@ -47,7 +47,7 @@ public class PathParser {
 
     /**
      * Parses <code>jcrPath</code> into a <code>Path</code> object using
-     * <code>resolver</code> to convert prefixes into namespace URI's. If
+     * <code>resolver</code> to convert prefixes into namespace URIs. If
      * resolver is <code>null</code> this method only checks the format of the
      * passed String and returns <code>null</code>.
      *
@@ -66,7 +66,7 @@ public class PathParser {
 
     /**
      * Parses <code>jcrPath</code> into a <code>Path</code> object using
-     * <code>resolver</code> to convert prefixes into namespace URI's. If the
+     * <code>resolver</code> to convert prefixes into namespace URIs. If the
      * specified <code>jcrPath</code> is an identifier based absolute path
      * beginning with an identifier segment the specified
      * <code>IdentifierResolver</code> will be used to resolve it to an
@@ -94,7 +94,7 @@ public class PathParser {
 
     /**
      * Parses <code>jcrPath</code> into a <code>Path</code> object using
-     * <code>resolver</code> to convert prefixes into namespace URI's. If the
+     * <code>resolver</code> to convert prefixes into namespace URIs. If the
      * specified <code>jcrPath</code> is an identifier based absolute path
      * beginning with an identifier segment the specified
      * <code>IdentifierResolver</code> will be used to resolve it to an
@@ -132,7 +132,7 @@ public class PathParser {
      * @param parent   the parent path
      * @param jcrPath  the JCR path
      * @param resolver the namespace resolver to get prefixes for namespace
-     *                 URI's.
+     *                 URIs.
      * @param factory
      * @return the <code>Path</code> object.
      * @throws MalformedPathException If the <code>jcrPath</code> is malformed.
@@ -251,6 +251,7 @@ public class PathParser {
 
         while (pos <= len) {
             char c = pos == len ? EOF : jcrPath.charAt(pos);
+            char rawCharacter = c;
             pos++;
             // special check for whitespace
             if (c != ' ' && Character.isWhitespace(c)) {
@@ -262,6 +263,10 @@ public class PathParser {
                     if (state == STATE_PREFIX_START && c != EOF) {
                         throw new MalformedPathException("'" + jcrPath + "' is not a valid path. double slash '//' not allowed.");
                     }
+                    if (state == STATE_URI && c == EOF) {
+                        // this handles the case where URI state was entered but the end of the segment was reached (JCR-3562)
+                        state = STATE_URI_END;
+                    }
                     if (state == STATE_PREFIX
                             || state == STATE_NAME
                             || state == STATE_INDEX_END
@@ -289,7 +294,7 @@ public class PathParser {
                         index = Path.INDEX_UNDEFINED;
                     } else if (state == STATE_IDENTIFIER) {
                         if (c == EOF) {
-                            // eof identifier reached                            
+                            // eof identifier reached
                             if (jcrPath.charAt(pos - 2) != ']') {
                                 throw new MalformedPathException("'" + jcrPath + "' is not a valid path: Unterminated identifier segment.");
                             }
@@ -390,7 +395,9 @@ public class PathParser {
 
                 case '\t':
                     if (state != STATE_IDENTIFIER) {
-                        throw new MalformedPathException("'" + jcrPath + "' is not a valid path. Whitespace not a allowed in name.");
+                        String message = String.format("'%s' is not a valid path. Whitespace other than SP (U+0020) not a allowed in a name, but U+%04x was found at position %d.",
+                                            jcrPath, (long) rawCharacter, pos - 1);
+                        throw new MalformedPathException(message);
                     }
                 case '*':
                 case '|':
@@ -414,7 +421,7 @@ public class PathParser {
                         state = STATE_URI_END;
                     }
                     break;
-                
+
                 default:
                     if (state == STATE_PREFIX_START || state == STATE_DOT || state == STATE_DOTDOT) {
                         state = STATE_PREFIX;
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/iterator/Transformer.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/iterator/Transformer.java
index fc4bb2f..058605b 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/iterator/Transformer.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/iterator/Transformer.java
@@ -27,7 +27,7 @@ public interface Transformer<A, R> {
     /**
      * Transforms the input object (leaving it unchanged) into some output object.
      *
-     * @param input  the object to be transformed, should be left unchanged
+     * @param argument  the object to be transformed, should be left unchanged
      * @return a transformed object
      */
     public R transform(A argument);
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/lock/Locked.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/lock/Locked.java
index b2c3c70..b944795 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/lock/Locked.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/lock/Locked.java
@@ -39,7 +39,7 @@ import javax.jcr.lock.LockException;
  * ensuring that the modification will never fail with an {@link
  * javax.jcr.InvalidItemStateException}. This utility can be used with any
  * JCR Repository, not just Jackrabbit.
- * <p/>
+ * <p>
  * The following example shows how this utility can be used to implement
  * a persistent counter:
  * <pre>
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/logging/AbstractLogger.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/logging/AbstractLogger.java
index a2e16b3..1493e4f 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/logging/AbstractLogger.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/logging/AbstractLogger.java
@@ -90,14 +90,14 @@ public class AbstractLogger {
     }
 
     /**
-     * Type of thunk used in {@link AbstractLogger#execute(Callable, String, Object[])
+     * Type of thunk used in {@link AbstractLogger#execute(Callable, String, Object[])}
      */
     protected interface Callable {
         public Object call() throws RepositoryException;
     }
 
     /**
-     * Type of thunk used in {@link AbstractLogger#execute(SafeCallable, String, Object[])
+     * Type of thunk used in {@link AbstractLogger#execute(SafeCallable, String, Object[])}
      */
     protected interface SafeCallable {
         public Object call();
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/logging/BatchLogger.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/logging/BatchLogger.java
index 0430137..d831607 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/logging/BatchLogger.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/logging/BatchLogger.java
@@ -24,6 +24,7 @@ import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.NodeId;
 import org.apache.jackrabbit.spi.PropertyId;
 import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.spi.Tree;
 
 /**
  * Log wrapper for a {@link Batch}.
@@ -141,5 +142,13 @@ public class BatchLogger extends AbstractLogger implements Batch {
             }}, "move(NodeId, NodeId, Name)", new Object[]{srcNodeId, destParentNodeId, destName});
     }
 
-
+    @Override
+    public void  setTree(final NodeId parentId, final Tree contentTree)
+            throws RepositoryException {
+        execute(new Callable() {
+            public Object call() throws RepositoryException {
+                batch.setTree(parentId, contentTree);
+                return null;
+            }}, "setTree(NodeId, Tree)", new Object[]{parentId, contentTree});
+    }
 }
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/logging/RepositoryServiceLogger.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/logging/RepositoryServiceLogger.java
index c3a7630..fc9a15d 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/logging/RepositoryServiceLogger.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/logging/RepositoryServiceLogger.java
@@ -42,6 +42,7 @@ import org.apache.jackrabbit.spi.NodeId;
 import org.apache.jackrabbit.spi.NodeInfo;
 import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.PathFactory;
+import org.apache.jackrabbit.spi.PrivilegeDefinition;
 import org.apache.jackrabbit.spi.PropertyId;
 import org.apache.jackrabbit.spi.PropertyInfo;
 import org.apache.jackrabbit.spi.QNodeDefinition;
@@ -53,6 +54,7 @@ import org.apache.jackrabbit.spi.QueryInfo;
 import org.apache.jackrabbit.spi.RepositoryService;
 import org.apache.jackrabbit.spi.SessionInfo;
 import org.apache.jackrabbit.spi.Subscription;
+import org.apache.jackrabbit.spi.Tree;
 
 /**
  * Log wrapper for a {@link RepositoryService}.
@@ -183,6 +185,31 @@ public class RepositoryServiceLogger extends AbstractLogger implements Repositor
         }, "isGranted(SessionInfo, ItemId, String[])", new Object[] { unwrap(sessionInfo), itemId, actions });
     }
 
+    @Override
+    public PrivilegeDefinition[] getPrivilegeDefinitions(final SessionInfo sessionInfo) throws RepositoryException {
+        return (PrivilegeDefinition[]) execute(new Callable() {
+            public Object call() throws RepositoryException {
+                return service.getPrivilegeDefinitions(unwrap(sessionInfo));
+            }
+        }, "getSupportedPrivileges(SessionInfo)", new Object[]{unwrap(sessionInfo)});
+    }
+
+    public PrivilegeDefinition[] getSupportedPrivileges(final SessionInfo sessionInfo, final NodeId nodeId) throws RepositoryException {
+        return (PrivilegeDefinition[]) execute(new Callable() {
+            public Object call() throws RepositoryException {
+                return service.getSupportedPrivileges(unwrap(sessionInfo), nodeId);
+            }
+        }, "getSupportedPrivileges(SessionInfo, NodeId)", new Object[]{unwrap(sessionInfo), nodeId});
+    }
+
+    public Name[] getPrivilegeNames(final SessionInfo sessionInfo, final NodeId nodeId) throws RepositoryException {
+        return (Name[]) execute(new Callable() {
+            public Object call() throws RepositoryException {
+                return service.getPrivilegeNames(unwrap(sessionInfo), nodeId);
+            }
+        }, "getPrivileges(SessionInfo, NodeId)", new Object[]{unwrap(sessionInfo), nodeId});
+    }
+
     public QNodeDefinition getNodeDefinition(final SessionInfo sessionInfo, final NodeId nodeId)
             throws RepositoryException {
 
@@ -269,6 +296,14 @@ public class RepositoryServiceLogger extends AbstractLogger implements Repositor
         }, "submit(Batch)", new Object[]{unwrap(batch)});
     }
 
+    @Override
+    public Tree createTree(final SessionInfo sessionInfo, final Batch batch, final Name nodeName, final Name primaryTypeName, final String uniqueId) throws RepositoryException {
+            return (Tree) execute(new Callable() {
+                public Object call() throws RepositoryException {
+                    return service.createTree(sessionInfo, batch, nodeName, primaryTypeName, uniqueId);
+                }}, "createTree(SessionInfo, Batch, Name, Name, String)", new Object[]{sessionInfo, batch, nodeName, primaryTypeName, uniqueId});
+    }
+
     public void importXml(final SessionInfo sessionInfo, final NodeId parentId, final InputStream xmlStream,
             final int uuidBehaviour) throws RepositoryException {
 
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/name/NameConstants.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/name/NameConstants.java
index 67e4a01..d1cfa36 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/name/NameConstants.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/name/NameConstants.java
@@ -134,7 +134,14 @@ public class NameConstants {
      */
     public static final Name JCR_ID = FACTORY.create(Name.NS_JCR_URI, "id");
 
-    /** jcr:title */
+    /**
+     * jcr:description
+     */
+    public static final Name JCR_DESCRIPTION = FACTORY.create(Property.JCR_DESCRIPTION);
+
+    /**
+     * jcr:title
+     */
     public static final Name JCR_TITLE = FACTORY.create(Property.JCR_TITLE);
 
     //--------------------------------------< xml related item name constants >
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/name/NameFactoryImpl.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/name/NameFactoryImpl.java
index a3b6b97..0e8d0d6 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/name/NameFactoryImpl.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/name/NameFactoryImpl.java
@@ -132,7 +132,7 @@ public class NameFactoryImpl implements NameFactory {
         /**
          * Returns the string representation of this <code>Name</code> in the
          * following format:
-         * <p/>
+         * <p>
          * <code><b>{</b>namespaceURI<b>}</b>localName</code>
          *
          * @return the string representation of this <code>Name</code>.
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/namespace/RegistryNamespaceResolver.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/namespace/RegistryNamespaceResolver.java
index 1e4652e..e40df9a 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/namespace/RegistryNamespaceResolver.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/namespace/RegistryNamespaceResolver.java
@@ -1,66 +1,66 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.spi.commons.namespace;
-
-import javax.jcr.NamespaceException;
-import javax.jcr.NamespaceRegistry;
-import javax.jcr.RepositoryException;
-
-/**
- * Namespace resolver based on the repository-wide namespace mappings
- * stored in a namespace registry.
- */
-public class RegistryNamespaceResolver implements NamespaceResolver {
-
-    /**
-     * Namespace registry
-     */
-    private final NamespaceRegistry registry;
-
-    /**
-     * Creates a new namespace resolver based on the given namespace registry.
-     * 
-     * @param registry namespace registry
-     */
-    public RegistryNamespaceResolver(NamespaceRegistry registry) {
-        this.registry = registry;
-    }
-
-    public String getPrefix(String uri) throws NamespaceException {
-        try {
-            return registry.getPrefix(uri);
-        } catch (RepositoryException e) {
-            if (!(e instanceof NamespaceException)) {
-                e = new NamespaceException(
-                        "Failed to resolve namespace URI: " + uri, e);
-            }
-            throw (NamespaceException) e;
-        }
-    }
-
-    public String getURI(String prefix) throws NamespaceException {
-        try {
-            return registry.getURI(prefix);
-        } catch (RepositoryException e) {
-            if (!(e instanceof NamespaceException)) {
-                e = new NamespaceException(
-                        "Failed to resolve namespace prefix: " + prefix, e);
-            }
-            throw (NamespaceException) e;
-        }
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.spi.commons.namespace;
+
+import javax.jcr.NamespaceException;
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.RepositoryException;
+
+/**
+ * Namespace resolver based on the repository-wide namespace mappings
+ * stored in a namespace registry.
+ */
+public class RegistryNamespaceResolver implements NamespaceResolver {
+
+    /**
+     * Namespace registry
+     */
+    private final NamespaceRegistry registry;
+
+    /**
+     * Creates a new namespace resolver based on the given namespace registry.
+     * 
+     * @param registry namespace registry
+     */
+    public RegistryNamespaceResolver(NamespaceRegistry registry) {
+        this.registry = registry;
+    }
+
+    public String getPrefix(String uri) throws NamespaceException {
+        try {
+            return registry.getPrefix(uri);
+        } catch (RepositoryException e) {
+            if (!(e instanceof NamespaceException)) {
+                e = new NamespaceException(
+                        "Failed to resolve namespace URI: " + uri, e);
+            }
+            throw (NamespaceException) e;
+        }
+    }
+
+    public String getURI(String prefix) throws NamespaceException {
+        try {
+            return registry.getURI(prefix);
+        } catch (RepositoryException e) {
+            if (!(e instanceof NamespaceException)) {
+                e = new NamespaceException(
+                        "Failed to resolve namespace prefix: " + prefix, e);
+            }
+            throw (NamespaceException) e;
+        }
+    }
+}
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/NodeTypeDefDiff.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/NodeTypeDefDiff.java
index 5d4a106..574df8f 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/NodeTypeDefDiff.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/NodeTypeDefDiff.java
@@ -27,21 +27,25 @@ import org.apache.jackrabbit.spi.commons.name.NameConstants;
 import javax.jcr.PropertyType;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * A <code>NodeTypeDefDiff</code> represents the result of the comparison of
  * two node type definitions.
- * <p/>
+ * <p>
  * The result of the comparison can be categorized as one of the following types:
- * <p/>
+ * <p>
  * <b><code>NONE</code></b> indicates that there is no modification at all.
- * <p/>
+ * <p>
  * A <b><code>TRIVIAL</code></b> modification has no impact on the consistency
  * of existing content. The following modifications are considered
  * <code>TRIVIAL</code>:
@@ -63,7 +67,7 @@ import java.util.Set;
  * <li>changing specific property <code>requiredType</code> to <code>undefined</code>
  * <li>changing property <code>multiple</code> flag to <code>true</code>
  * </ul>
- * <p/>
+ * <p>
  * A <b><code>MAJOR</code></b> modification potentially <i>affects</i> the
  * consistency of existing content.
  *
@@ -280,50 +284,43 @@ public class NodeTypeDefDiff {
      */
     private int buildChildNodeDefDiffs() {
         int maxType = NONE;
-        QNodeDefinition[] cnda1 = oldDef.getChildNodeDefs();
-        Map<QNodeDefinitionId, QNodeDefinition> defs1 = new HashMap<QNodeDefinitionId, QNodeDefinition>();
-        for (QNodeDefinition def1 : cnda1) {
-            defs1.put(new QNodeDefinitionId(def1), def1);
-        }
+        final Map<QNodeDefinitionId, List<QNodeDefinition>> oldDefs = collectChildNodeDefs(oldDef.getChildNodeDefs());
+        final Map<QNodeDefinitionId, List<QNodeDefinition>> newDefs = collectChildNodeDefs(newDef.getChildNodeDefs());
 
-        QNodeDefinition[] cnda2 = newDef.getChildNodeDefs();
-        Map<QNodeDefinitionId, QNodeDefinition> defs2 = new HashMap<QNodeDefinitionId, QNodeDefinition>();
-        for (QNodeDefinition def2 : cnda2) {
-            defs2.put(new QNodeDefinitionId(def2), def2);
+        for (QNodeDefinitionId defId : oldDefs.keySet()) {
+            final ChildNodeDefDiffs childNodeDefDiffs = new ChildNodeDefDiffs(oldDefs.get(defId), newDefs.get(defId));
+            this.childNodeDefDiffs.addAll(childNodeDefDiffs.getChildNodeDefDiffs());
+            newDefs.remove(defId);
         }
 
-        /**
-         * walk through defs1 and process all entries found in
-         * both defs1 & defs2 and those found only in defs1
-         */
-        for (Map.Entry<QNodeDefinitionId, QNodeDefinition> entry1 : defs1.entrySet()) {
-            QNodeDefinitionId id = entry1.getKey();
-            QNodeDefinition def1 = entry1.getValue();
-            QNodeDefinition def2 = defs2.get(id);
-            ChildNodeDefDiff diff = new ChildNodeDefDiff(def1, def2);
-            if (diff.getType() > maxType) {
-                maxType = diff.getType();
-            }
-            childNodeDefDiffs.add(diff);
-            defs2.remove(id);
+        for (QNodeDefinitionId defId : newDefs.keySet()) {
+            final ChildNodeDefDiffs childNodeDefDiffs = new ChildNodeDefDiffs(null, newDefs.get(defId));
+            this.childNodeDefDiffs.addAll(childNodeDefDiffs.getChildNodeDefDiffs());
         }
 
-        /**
-         * defs2 by now only contains entries found in defs2 only;
-         * walk through defs2 and process all remaining entries
-         */
-        for (Map.Entry<QNodeDefinitionId, QNodeDefinition> entry2 : defs2.entrySet()) {
-            QNodeDefinition def2 = entry2.getValue();
-            ChildNodeDefDiff diff = new ChildNodeDefDiff(null, def2);
+        for (ChildNodeDefDiff diff : childNodeDefDiffs) {
             if (diff.getType() > maxType) {
                 maxType = diff.getType();
             }
-            childNodeDefDiffs.add(diff);
         }
 
         return maxType;
     }
 
+    private Map<QNodeDefinitionId, List<QNodeDefinition>> collectChildNodeDefs(final QNodeDefinition[] cnda1) {
+        Map<QNodeDefinitionId, List<QNodeDefinition>> defs1 = new HashMap<QNodeDefinitionId, List<QNodeDefinition>>();
+        for (QNodeDefinition def1 : cnda1) {
+            final QNodeDefinitionId def1Id = new QNodeDefinitionId(def1);
+            List<QNodeDefinition> list = defs1.get(def1Id);
+            if (list == null) {
+                list = new ArrayList<QNodeDefinition>();
+                defs1.put(def1Id, list);
+            }
+            list.add(def1);
+        }
+        return defs1;
+    }
+
     @Override
     public String toString() {
         String result = getClass().getName() + "[\n\tnodeTypeName="
@@ -602,11 +599,14 @@ public class NodeTypeDefDiff {
                 // no need to check defaultPrimaryType (TRIVIAL change)
 
                 if (type == TRIVIAL) {
-                    List<Name> l1 = Arrays.asList(getOldDef().getRequiredPrimaryTypes());
-                    List<Name> l2 = Arrays.asList(getNewDef().getRequiredPrimaryTypes());
-                    if (!l1.equals(l2)) {
+                    Set<Name> s1 = new HashSet<Name>(Arrays.asList(getOldDef().getRequiredPrimaryTypes()));
+                    Set<Name> s2 = new HashSet<Name>(Arrays.asList(getNewDef().getRequiredPrimaryTypes()));
+                    // normalize sets by removing nt:base (adding/removing nt:base is irrelevant for the diff)
+                    s1.remove(NameConstants.NT_BASE);
+                    s2.remove(NameConstants.NT_BASE);
+                    if (!s1.equals(s2)) {
                         // requiredPrimaryTypes have been modified
-                        if (l1.containsAll(l2)) {
+                        if (s1.containsAll(s2)) {
                             // old list is a superset of new list
                             // => removed requiredPrimaryType (TRIVIAL change)
                             type = TRIVIAL;
@@ -628,16 +628,12 @@ public class NodeTypeDefDiff {
 
         Name declaringNodeType;
         Name name;
-        int requiredType;
         boolean definesResidual;
-        boolean isMultiple;
 
         QPropertyDefinitionId(QPropertyDefinition def) {
             declaringNodeType = def.getDeclaringNodeType();
             name = def.getName();
-            requiredType = def.getRequiredType();
             definesResidual = def.definesResidual();
-            isMultiple = def.isMultiple();
         }
 
         //---------------------------------------< java.lang.Object overrides >
@@ -650,9 +646,7 @@ public class NodeTypeDefDiff {
                 QPropertyDefinitionId other = (QPropertyDefinitionId) obj;
                 return declaringNodeType.equals(other.declaringNodeType)
                         && name.equals(other.name)
-                        && requiredType == other.requiredType
-                        && definesResidual == other.definesResidual
-                        && isMultiple == other.isMultiple;
+                        && definesResidual == other.definesResidual;
             }
             return false;
         }
@@ -663,8 +657,6 @@ public class NodeTypeDefDiff {
             h = 37 * h + declaringNodeType.hashCode();
             h = 37 * h + name.hashCode();
             h = 37 * h + (definesResidual ? 11 : 43);
-            h = 37 * h + (isMultiple ? 11 : 43);
-            h = 37 * h + requiredType;
             return h;
         }
     }
@@ -676,16 +668,10 @@ public class NodeTypeDefDiff {
 
         Name declaringNodeType;
         Name name;
-        Name[] requiredPrimaryTypes;
 
         QNodeDefinitionId(QNodeDefinition def) {
             declaringNodeType = def.getDeclaringNodeType();
             name = def.getName();
-            requiredPrimaryTypes = def.getRequiredPrimaryTypes();
-            if (requiredPrimaryTypes == null || requiredPrimaryTypes.length == 0) {
-                requiredPrimaryTypes = new Name[]{NameConstants.NT_BASE};
-            }
-            Arrays.sort(requiredPrimaryTypes);
         }
 
         //---------------------------------------< java.lang.Object overrides >
@@ -697,8 +683,7 @@ public class NodeTypeDefDiff {
             if (obj instanceof QNodeDefinitionId) {
                 QNodeDefinitionId other = (QNodeDefinitionId) obj;
                 return declaringNodeType.equals(other.declaringNodeType)
-                        && name.equals(other.name)
-                        && Arrays.equals(requiredPrimaryTypes, other.requiredPrimaryTypes);
+                        && name.equals(other.name);
             }
             return false;
         }
@@ -708,10 +693,92 @@ public class NodeTypeDefDiff {
             int h = 17;
             h = 37 * h + declaringNodeType.hashCode();
             h = 37 * h + name.hashCode();
-            for (int i = 0; i < requiredPrimaryTypes.length; i++) {
-                h = 37 * h + requiredPrimaryTypes[i].hashCode();
-            }
             return h;
         }
     }
+
+    private class ChildNodeDefDiffs {
+
+        private final List<QNodeDefinition> defs1;
+        private final List<QNodeDefinition> defs2;
+
+        private ChildNodeDefDiffs(final List<QNodeDefinition> defs1, final List<QNodeDefinition> defs2) {
+            this.defs1 = defs1 != null ? defs1 : Collections.<QNodeDefinition>emptyList();
+            this.defs2 = defs2 != null ? defs2 : Collections.<QNodeDefinition>emptyList();
+        }
+
+        private Collection<ChildNodeDefDiff> getChildNodeDefDiffs() {
+            // gather all possible combinations of diffs
+            final List<ChildNodeDefDiff> diffs = new ArrayList<ChildNodeDefDiff>();
+            for (QNodeDefinition def1 : defs1) {
+                for (QNodeDefinition def2 : defs2) {
+                    diffs.add(new ChildNodeDefDiff(def1, def2));
+                }
+            }
+            if (defs2.size() < defs1.size()) {
+                for (QNodeDefinition def1 : defs1) {
+                    diffs.add(new ChildNodeDefDiff(def1, null));
+                }
+            }
+            if (defs1.size() < defs2.size()) {
+                for (QNodeDefinition def2 : defs2) {
+                    diffs.add(new ChildNodeDefDiff(null, def2));
+                }
+            }
+            // sort them according to decreasing compatibility
+            Collections.sort(diffs, new Comparator<ChildNodeDefDiff>() {
+                @Override
+                public int compare(final ChildNodeDefDiff o1, final ChildNodeDefDiff o2) {
+                    return o1.getType() - o2.getType();
+                }
+            });
+            // select the most compatible ones
+            final int size = defs1.size() > defs2.size() ? defs1.size() : defs2.size();
+            AtomicInteger allowedNewNull = new AtomicInteger(defs1.size() - defs2.size());
+            AtomicInteger allowedOldNull = new AtomicInteger(defs2.size() - defs1.size());
+            final List<ChildNodeDefDiff> results = new ArrayList<ChildNodeDefDiff>();
+            for (ChildNodeDefDiff diff : diffs) {
+                if (!alreadyMatched(results, diff.getNewDef(), diff.getOldDef(), allowedNewNull, allowedOldNull)) {
+                    results.add(diff);
+                    if (diff.getNewDef() == null) {
+                        allowedNewNull.decrementAndGet();
+                    }
+                    if (diff.getOldDef() == null) {
+                        allowedOldNull.decrementAndGet();
+                    }
+                }
+                if (results.size() == size) {
+                    break;
+                }
+            }
+            return results;
+        }
+
+        private boolean alreadyMatched(final List<ChildNodeDefDiff> result, final QNodeDefinition newDef, final QNodeDefinition oldDef, final AtomicInteger allowedNewNull, final AtomicInteger allowedOldNull) {
+            boolean containsNewDef = false, containsOldDef = false;
+            for (ChildNodeDefDiff d : result) {
+                if (d.getNewDef() != null && d.getNewDef().equals(newDef)) {
+                    containsNewDef = true;
+                    break;
+                }
+                if (d.getOldDef() != null && d.getOldDef().equals(oldDef)) {
+                    containsOldDef = true;
+                    break;
+                }
+            }
+            if (oldDef == null) {
+                if (allowedOldNull.get() < 1) {
+                    containsOldDef = true;
+                }
+            }
+            if (newDef == null) {
+                if (allowedNewNull.get() < 1) {
+                    containsNewDef = true;
+                }
+            }
+
+            return containsNewDef || containsOldDef;
+        }
+    }
+
 }
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/NodeTypeDefinitionImpl.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/NodeTypeDefinitionImpl.java
index d7f82fe..9da0da7 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/NodeTypeDefinitionImpl.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/NodeTypeDefinitionImpl.java
@@ -154,7 +154,7 @@ public class NodeTypeDefinitionImpl implements NodeTypeDefinition {
             } catch (NamespaceException e) {
                 // should never get here
                 log.error("invalid node type name: " + stNames[i], e);
-                dstn[i] = stNames.toString();
+                dstn[i] = stNames[i].toString();
             }
         }
         return dstn;
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/constraint/ValueConstraint.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/constraint/ValueConstraint.java
index 24e19c6..17e7bc1 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/constraint/ValueConstraint.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/nodetype/constraint/ValueConstraint.java
@@ -54,7 +54,7 @@ public abstract class ValueConstraint implements QValueConstraint {
     /**
      * For constraints that are not namespace prefix mapping sensitive this
      * method returns the same result as <code>{@link #getString()}</code>.
-     * <p/>
+     * <p>
      * Those that are namespace prefix mapping sensitive (e.g.
      * <code>NameConstraint</code>, <code>PathConstraint</code> and
      * <code>ReferenceConstraint</code>) use the given <code>nsResolver</code>
@@ -271,7 +271,7 @@ public abstract class ValueConstraint implements QValueConstraint {
     /**
      * Tests if the value constraints defined in the property definition
      * <code>pd</code> are satisfied by the the specified <code>values</code>.
-     * <p/>
+     * <p>
      * Note that the <i>protected</i> flag is not checked. Also note that no
      * type conversions are attempted if the type of the given values does not
      * match the required type as specified in the given definition.
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/package-info.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/package-info.java
index 7aa8d4f..b24153c 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/package-info.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/package-info.java
@@ -14,5 +14,5 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- at aQute.bnd.annotation.Version("2.4.0")
+ at aQute.bnd.annotation.Version("2.4.1")
 package org.apache.jackrabbit.spi.commons;
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/LocationStepQueryNode.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/LocationStepQueryNode.java
index a956518..8c86ce3 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/LocationStepQueryNode.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/LocationStepQueryNode.java
@@ -23,7 +23,7 @@ import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
 
 /**
  * Defines a location step for querying the path of a node.
- * <p/>
+ * <p>
  * <code>
  * /foo  -> descendants = false, nameTest = foo<br>
  * //foo -> descendants = true, nameTest = foo<br>
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/QueryParser.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/QueryParser.java
index 10d0490..58b2825 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/QueryParser.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/QueryParser.java
@@ -36,7 +36,7 @@ public class QueryParser {
     /**
      * Parses a query <code>statement</code> according to a query
      * <code>language</code> into a query tree.
-     * <p/>
+     * <p>
      * <code>language</code> must be one of: {@link javax.jcr.query.Query#SQL},
      * {@link javax.jcr.query.Query#XPATH}.
      *
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/qom/ColumnImpl.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/qom/ColumnImpl.java
index 42d9d68..b3ebb12 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/qom/ColumnImpl.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/qom/ColumnImpl.java
@@ -98,7 +98,7 @@ public class ColumnImpl extends AbstractQOMNode implements Column {
 
     /**
      * Gets the column name.
-     * <p/>
+     * <p>
      *
      * @return the column name; must be null if <code>getPropertyName</code> is
      *         null and non-null otherwise
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/qom/QueryObjectModelFactoryImpl.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/qom/QueryObjectModelFactoryImpl.java
index 50f9dc1..d6f32cc 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/qom/QueryObjectModelFactoryImpl.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/qom/QueryObjectModelFactoryImpl.java
@@ -91,7 +91,7 @@ public abstract class QueryObjectModelFactoryImpl implements QueryObjectModelFac
 
     /**
      * Creates a query with one selector.
-     * <p/>
+     * <p>
      * The specified selector will be the <i>default selector</i> of the query.
      *
      * @param selector   the selector; non-null
@@ -114,7 +114,7 @@ public abstract class QueryObjectModelFactoryImpl implements QueryObjectModelFac
 
     /**
      * Creates a query with one or more selectors.
-     * <p/>
+     * <p>
      * If <code>source</code> is a selector, that selector is the <i>default
      * selector</i> of the query.  Otherwise the query does not have a default
      * selector.
@@ -175,7 +175,7 @@ public abstract class QueryObjectModelFactoryImpl implements QueryObjectModelFac
 
     /**
      * Selects a subset of the nodes in the repository based on node type.
-     * <p/>
+     * <p>
      * The selector name is the node type name.
      *
      * @param nodeTypeName the name of the required node type; non-null
@@ -895,7 +895,7 @@ public abstract class QueryObjectModelFactoryImpl implements QueryObjectModelFac
     /**
      * Identifies a property in the default selector to include in the tabular
      * view of query results.
-     * <p/>
+     * <p>
      * The column name is the property name.
      *
      * @param propertyName the property name, or null to include a column for
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/qom/SelectorImpl.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/qom/SelectorImpl.java
index 6279787..1435697 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/qom/SelectorImpl.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/qom/SelectorImpl.java
@@ -55,7 +55,7 @@ public class SelectorImpl extends SourceImpl implements Selector {
 
     /**
      * Gets the selector name.
-     * <p/>
+     * <p>
      * A selector's name can be used elsewhere in the query to identify the
      * selector.
      *
@@ -87,7 +87,7 @@ public class SelectorImpl extends SourceImpl implements Selector {
 
     /**
      * Gets the selector name.
-     * <p/>
+     * <p>
      * A selector's name can be used elsewhere in the query to identify the
      * selector.
      *
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/sql/JCRSQLQueryBuilder.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/sql/JCRSQLQueryBuilder.java
index c410c3c..0183518 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/sql/JCRSQLQueryBuilder.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/sql/JCRSQLQueryBuilder.java
@@ -800,7 +800,7 @@ public class JCRSQLQueryBuilder implements JCRSQLParserVisitor {
      * Extends the <code>PathQueryNode</code> with merging capability. A
      * <code>PathQueryNode</code> <code>n1</code> can be merged with another
      * node <code>n2</code> in the following case:
-     * <p/>
+     * <p>
      * <code>n1</code> contains a location step at position <code>X</code> with
      * a name test that matches any node and has the descending flag set. Where
      * <code>X</code> < number of location steps.
@@ -810,12 +810,12 @@ public class JCRSQLQueryBuilder implements JCRSQLParserVisitor {
      * The merged node then contains a location step at position <code>X</code>
      * with the name test of the location step at position <code>X+1</code> and
      * the descending flag set.
-     * <p/>
+     * <p>
      * The following path patterns:<br/>
      * <code>/foo/%/bar</code> OR <code>/foo/bar</code><br/>
      * are merged into:<br/>
      * <code>/foo//bar</code>.
-     * <p/>
+     * <p>
      * The path patterns:<br/>
      * <code>/foo/%</code> AND NOT <code>/foo/%/%</code><br/>
      * are merged into:<br/>
@@ -866,7 +866,7 @@ public class JCRSQLQueryBuilder implements JCRSQLParserVisitor {
         /**
          * Merges two nodes into a node which selects any child nodes of a
          * given node.
-         * <p/>
+         * <p>
          * Example:<br/>
          * The path patterns:<br/>
          * <code>/foo/%</code> AND NOT <code>/foo/%/%</code><br/>
@@ -931,7 +931,7 @@ public class JCRSQLQueryBuilder implements JCRSQLParserVisitor {
         /**
          * Merges two nodes into one node selecting a node on the
          * descendant-or-self axis.
-         * <p/>
+         * <p>
          * Example:<br/>
          * The following path patterns:<br/>
          * <code>/foo/%/bar</code> OR <code>/foo/bar</code><br/>
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/QueryFormat.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/QueryFormat.java
index bfbde86..d345adc 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/QueryFormat.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/QueryFormat.java
@@ -348,7 +348,7 @@ class QueryFormat implements QueryNodeVisitor, QueryConstants {
             PathQueryNode relPath = node.getRelativePath();
             if (relPath == null) {
                 propPath.append(".");
-            } else if (relPath.getNumOperands() > 0 && relPath.getPathSteps()[0].getNameTest().equals(XPathQueryBuilder.FN_POSITION_FULL)) {
+            } else if (relPath.getNumOperands() > 0 && XPathQueryBuilder.FN_POSITION_FULL.equals(relPath.getPathSteps()[0].getNameTest())) {
                 propPath.append(resolver.getJCRName(XPathQueryBuilder.FN_POSITION_FULL));
             } else {
                 LocationStepQueryNode[] steps = relPath.getPathSteps();
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/XPathQueryBuilder.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/XPathQueryBuilder.java
index 19c852b..013bf1a 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/XPathQueryBuilder.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/query/xpath/XPathQueryBuilder.java
@@ -85,6 +85,11 @@ public class XPathQueryBuilder implements XPathVisitor, XPathTreeConstants {
     static final Name FN_UPPER_CASE = NAME_FACTORY.create(NS_FN_URI, "upper-case");
 
     /**
+     * Name for 'rep:normalize'
+     */
+    static final Name REP_NORMALIZE = NAME_FACTORY.create(Name.NS_REP_URI, "normalize");
+
+    /**
      * Name for 'not' as defined in XPath 1.0 (no prefix)
      */
     static final Name FN_NOT_10 = NAME_FACTORY.create("", "not");
@@ -425,8 +430,13 @@ public class XPathQueryBuilder implements XPathVisitor, XPathTreeConstants {
                         }
                         PathQueryNode relPath = tmp.getRelativePath();
                         LocationStepQueryNode[] steps = relPath.getPathSteps();
-                        
-                        tmpRelPath.addLast(steps[steps.length-1].getNameTest());
+
+                        Name nameTest = steps[steps.length-1].getNameTest();
+                        if (nameTest==null) {
+                        	// see LocationStepQueryNode javadoc on when getNameTest()==null: when it was a star (asterisk)
+                        	nameTest = RelationQueryNode.STAR_NAME_TEST;
+                        }
+						tmpRelPath.addLast(nameTest);
                     }
                 }
                 break;
@@ -956,7 +966,7 @@ public class XPathQueryBuilder implements XPathVisitor, XPathTreeConstants {
                     }
                     if (queryNode.getType() == QueryNode.TYPE_PATH) {
                         PathQueryNode pathNode = (PathQueryNode) queryNode;
-                        
+
                         pathNode.addPathStep(createDerefQueryNode(node, descendant, pathNode));
                     } else if (queryNode.getType() == QueryNode.TYPE_RELATION) {
                         RelationQueryNode relNode = (RelationQueryNode) queryNode;
@@ -1004,7 +1014,18 @@ public class XPathQueryBuilder implements XPathVisitor, XPathTreeConstants {
                         exceptions.add(new InvalidQueryException("Unsupported location for fn:upper-case()"));
                     }
                 } else {
-                    exceptions.add(new InvalidQueryException("Unsupported location for fn:upper-case()"));
+                    exceptions.add(new InvalidQueryException("Wrong number of argument for fn:upper-case()"));
+                }
+            } else if (REP_NORMALIZE.equals(funName)) {
+                if (node.jjtGetNumChildren() == 2) {
+                    if (queryNode.getType() == QueryNode.TYPE_ORDER) {
+                        ((OrderQueryNode) queryNode).setFunction(REP_NORMALIZE.getLocalName());
+                        node.childrenAccept(this, queryNode);
+                    } else {
+                        exceptions.add(new InvalidQueryException("Unsupported location for rep:normalize()"));
+                    }
+                } else {
+                    exceptions.add(new InvalidQueryException("Wrong number of argument for rep:normalize()"));
                 }
             } else if (REP_SIMILAR.equals(funName)) {
                 if (node.jjtGetNumChildren() == 3) {
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/tree/AbstractTree.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/tree/AbstractTree.java
new file mode 100644
index 0000000..b55f7f0
--- /dev/null
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/tree/AbstractTree.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.spi.commons.tree;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.Tree;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+
+public abstract class AbstractTree implements Tree {
+
+    private final Name nodeName;
+    private final Name ntName;
+    private final String uniqueId;
+
+    private final NamePathResolver resolver;
+
+    private List<Tree> children;
+
+    protected AbstractTree(Name nodeName, Name ntName, String uniqueId, NamePathResolver resolver) {
+        this.nodeName = nodeName;
+        this.ntName = ntName;
+        this.uniqueId = uniqueId;
+        this.children = new ArrayList<Tree>();
+        this.resolver = resolver;
+    }
+
+    protected NamePathResolver getResolver() {
+        return resolver;
+    }
+    
+    protected List<Tree> getChildren() {
+        return children;
+    }
+
+    protected abstract Tree createChild(Name name, Name primaryTypeName, String uniqueId);
+
+    //---------------------------------------------------------------< Tree >---
+    @Override
+    public Name getName() {
+        return nodeName;
+    }
+
+    @Override
+    public Name getPrimaryTypeName() {
+        return ntName;
+    }
+
+    @Override
+    public String getUniqueId() {
+        return uniqueId;
+    }
+
+    @Override
+    public Tree addChild(Name childName, Name primaryTypeName, String uniqueId) {
+        Tree child = createChild(childName, primaryTypeName, uniqueId);        
+        children.add(child);
+        
+        return child;
+    }
+}
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/AbstractQValue.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/AbstractQValue.java
index 393855f..a728b71 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/AbstractQValue.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/AbstractQValue.java
@@ -20,7 +20,6 @@ import org.apache.jackrabbit.spi.QValue;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.util.ISO8601;
-import org.apache.commons.io.IOUtils;
 
 import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
@@ -33,7 +32,11 @@ import java.math.BigDecimal;
 import java.net.URI;
 import java.io.InputStream;
 import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
 import java.io.Serializable;
+import java.io.StringWriter;
+import java.io.Writer;
 
 /**
  * <code>AbstractQValue</code>...
@@ -42,7 +45,7 @@ public abstract class AbstractQValue implements QValue, Serializable {
 
     private static final long serialVersionUID = 6976433831974695272L;
 
-    protected Object val;
+    protected final Object val;
     protected final int type;
 
     /**
@@ -57,7 +60,7 @@ public abstract class AbstractQValue implements QValue, Serializable {
         if (value == null) {
             throw new IllegalArgumentException("null value");
         }
-        val = value;
+        this.val = value;
         this.type = type;
     }
 
@@ -74,10 +77,13 @@ public abstract class AbstractQValue implements QValue, Serializable {
         if (value == null) {
             throw new IllegalArgumentException("null value");
         }
-        if (!(type == PropertyType.STRING || type == PropertyType.REFERENCE || type == PropertyType.WEAKREFERENCE)) {
+        if (!(type == PropertyType.STRING
+                || type == PropertyType.DATE // JCR-3083
+                || type == PropertyType.REFERENCE
+                || type == PropertyType.WEAKREFERENCE)) {
             throw new IllegalArgumentException();
         }
-        val = value;
+        this.val = value;
         this.type = type;
     }
 
@@ -336,13 +342,23 @@ public abstract class AbstractQValue implements QValue, Serializable {
      */
     public String getString() throws RepositoryException {
         if (type == PropertyType.BINARY) {
-            InputStream stream = getStream();
             try {
-                return IOUtils.toString(stream, "UTF-8");
+                InputStream stream = getStream();
+                try {
+                    Reader reader = new InputStreamReader(stream, "UTF-8");
+                    Writer writer = new StringWriter();
+                    char[] buffer = new char[1024];
+                    int n = reader.read(buffer);
+                    while (n != -1) {
+                        writer.write(buffer, 0, n);
+                        n = reader.read(buffer);
+                    }
+                    return writer.toString();
+                } finally {
+                    stream.close();
+                }
             } catch (IOException e) {
                 throw new RepositoryException("conversion from stream to string failed", e);
-            } finally {
-                IOUtils.closeQuietly(stream);
             }
         } else if (type == PropertyType.DATE) {
             return (String) val;
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/QValueFactoryImpl.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/QValueFactoryImpl.java
index 66d7829..3c7ed3f 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/QValueFactoryImpl.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/QValueFactoryImpl.java
@@ -130,7 +130,7 @@ public class QValueFactoryImpl extends AbstractQValueFactory {
          * <code>InputStream</code>. The contents of the stream is spooled
          * to a temporary file or to a byte buffer if its size is smaller than
          * {@link #MAX_BUFFER_SIZE}.
-         * <p/>
+         * <p>
          * The new instance represents a <i>temporary</i> value whose dynamically
          * allocated resources will be freed explicitly on {@link #discard()}.
          *
@@ -147,7 +147,7 @@ public class QValueFactoryImpl extends AbstractQValueFactory {
          * <code>InputStream</code>. The contents of the stream is spooled
          * to a temporary file or to a byte buffer if its size is smaller than
          * {@link #MAX_BUFFER_SIZE}.
-         * <p/>
+         * <p>
          * The <code>temp</code> parameter governs whether dynamically allocated
          * resources will be freed explicitly on {@link #discard()}. Note that any
          * dynamically allocated resources (temp file/buffer) will be freed
diff --git a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/ValueFactoryQImpl.java b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/ValueFactoryQImpl.java
index 506a31e..7e03997 100644
--- a/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/ValueFactoryQImpl.java
+++ b/jackrabbit-spi-commons/src/main/java/org/apache/jackrabbit/spi/commons/value/ValueFactoryQImpl.java
@@ -30,7 +30,6 @@ import javax.jcr.Value;
 import javax.jcr.ValueFactory;
 import javax.jcr.ValueFormatException;
 
-import org.apache.commons.io.IOUtils;
 import org.apache.jackrabbit.spi.commons.conversion.IllegalNameException;
 import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException;
 import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
@@ -153,15 +152,16 @@ public class ValueFactoryQImpl implements ValueFactory {
      */
     public Value createValue(InputStream value) {
         try {
-            QValue qvalue = qfactory.create(value);
-            return new QValueValue(qvalue, resolver);
+            try {
+                QValue qvalue = qfactory.create(value);
+                return new QValueValue(qvalue, resolver);
+            } finally {
+                value.close(); // JCR-2903
+            }
         } catch (IOException ex) {
             throw new RuntimeException(ex);
         } catch (RepositoryException ex) {
             throw new RuntimeException(ex);
-        } finally {
-            // JCR-2903
-            IOUtils.closeQuietly(value);
         }
     }
 
@@ -206,15 +206,16 @@ public class ValueFactoryQImpl implements ValueFactory {
     public Binary createBinary(InputStream stream) throws RepositoryException {
         // TODO review/optimize/refactor
         try {
-            QValue qvalue = qfactory.create(stream);
-            return qvalue.getBinary();
+            try {
+                QValue qvalue = qfactory.create(stream);
+                return qvalue.getBinary();
+            } finally {
+                stream.close(); // JCR-2903
+            }
         } catch (IOException ex) {
             throw new RuntimeException(ex);
         } catch (RepositoryException ex) {
             throw new RuntimeException(ex);
-        } finally {
-            // JCR-2903
-            IOUtils.closeQuietly(stream);
         }
     }
 
@@ -240,4 +241,5 @@ public class ValueFactoryQImpl implements ValueFactory {
         QValue qvalue = qfactory.create(value.getUUID(), weak ? PropertyType.WEAKREFERENCE : PropertyType.REFERENCE);
         return new QValueValue(qvalue, resolver);
     }
+
 }
diff --git a/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/conversion/PathParserTest.java b/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/conversion/PathParserTest.java
index 1c2a5b7..3fb73ad 100644
--- a/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/conversion/PathParserTest.java
+++ b/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/conversion/PathParserTest.java
@@ -259,6 +259,7 @@ public class PathParserTest extends TestCase {
         paths.add("/:");
         paths.add("/*");
         paths.add("//");
+        paths.add("foo\u3000bar"); // non-ASCII whitespace
 
         for (String jcrPath : paths) {
             try {
@@ -382,9 +383,9 @@ public class PathParserTest extends TestCase {
             } catch (MalformedPathException e) {
                 // ok
             }
-        }       
+        }
     }
-    
+
     public void testIdentifierCheckFormat() throws RepositoryException {
         DummyIdentifierResolver idResolver = new DummyIdentifierResolver();
         List<String> valid = idResolver.getValidIdentifiers();
diff --git a/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/NodeTypeDefDiffTest.java b/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/NodeTypeDefDiffTest.java
new file mode 100644
index 0000000..bcbb857
--- /dev/null
+++ b/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/NodeTypeDefDiffTest.java
@@ -0,0 +1,139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.spi.commons.nodetype;
+
+import junit.framework.TestCase;
+
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.NameFactory;
+import org.apache.jackrabbit.spi.QNodeDefinition;
+import org.apache.jackrabbit.spi.QPropertyDefinition;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
+
+import javax.jcr.PropertyType;
+
+public class NodeTypeDefDiffTest extends TestCase {
+
+    private static final NameFactory FACTORY = NameFactoryImpl.getInstance();
+
+    private static final Name NODE_TYPE1 = FACTORY.create("{}nodeType1");
+    private static final Name PROP_NAME = FACTORY.create("{}prop");
+    private static final Name CHILD_NAME = FACTORY.create("{}child");
+
+    public void testChangedPropertyDefinition() throws Exception {
+        // old node type definition
+        QNodeTypeDefinitionBuilder oldDef = new QNodeTypeDefinitionBuilder();
+        oldDef.setName(NODE_TYPE1);
+        oldDef.setSupertypes(new Name[] { NameConstants.NT_BASE });
+
+        QPropertyDefinitionBuilder oldPropDef = new QPropertyDefinitionBuilder();
+        oldPropDef.setDeclaringNodeType(NODE_TYPE1);
+        oldPropDef.setName(PROP_NAME);
+        oldPropDef.setRequiredType(PropertyType.STRING);
+        oldPropDef.setMultiple(false);
+
+        oldDef.setPropertyDefs(new QPropertyDefinition[]{oldPropDef.build()});
+
+        QNodeDefinitionBuilder oldChildDef = new QNodeDefinitionBuilder();
+        oldChildDef.setRequiredPrimaryTypes(new Name[]{ NODE_TYPE1, NameConstants.NT_FOLDER });
+        oldChildDef.setName(CHILD_NAME);
+        oldChildDef.setDeclaringNodeType(oldDef.getName());
+
+        oldDef.setChildNodeDefs(new QNodeDefinition[] { oldChildDef.build() });
+
+        // new node type definition
+        QNodeTypeDefinitionBuilder newDef = new QNodeTypeDefinitionBuilder();
+        newDef.setName(NODE_TYPE1);
+        newDef.setSupertypes(new Name[] { NameConstants.NT_BASE });
+
+        QPropertyDefinitionBuilder newPropDef = new QPropertyDefinitionBuilder();
+        newPropDef.setDeclaringNodeType(NODE_TYPE1);
+        newPropDef.setName(PROP_NAME);
+        newPropDef.setRequiredType(PropertyType.UNDEFINED);
+        newPropDef.setMultiple(true);
+
+        newDef.setPropertyDefs(new QPropertyDefinition[]{newPropDef.build()});
+
+        QNodeDefinitionBuilder newChildDef = new QNodeDefinitionBuilder();
+        newChildDef.setRequiredPrimaryTypes(new Name[]{ NODE_TYPE1, NameConstants.NT_BASE });
+        newChildDef.setName(CHILD_NAME);
+        newChildDef.setDeclaringNodeType(oldDef.getName());
+
+        newDef.setChildNodeDefs(new QNodeDefinition[] { newChildDef.build() });
+
+        // change a property def isMultiple from false to true and requiredType STRING to UNDEFINED
+        // remove nt:folder from a node def's requiredPrimaryType constraint
+        NodeTypeDefDiff nodeTypeDefDiff = NodeTypeDefDiff.create(oldDef.build(), newDef.build());
+        assertTrue(nodeTypeDefDiff.isTrivial());
+
+        // change a property def isMultiple from true to false and requiredType UNDEFINED to STRING
+        // add nt:folder to a node def's requiredPrimaryType constraint
+        nodeTypeDefDiff = NodeTypeDefDiff.create(newDef.build(), oldDef.build());
+        assertTrue(nodeTypeDefDiff.isMajor());
+    }
+
+    public void testChangedSameNameChildNodeDefinition() throws Exception {
+        // old node type definition
+        QNodeTypeDefinitionBuilder oldDef = new QNodeTypeDefinitionBuilder();
+        oldDef.setName(NODE_TYPE1);
+        oldDef.setSupertypes(new Name[] { NameConstants.NT_BASE });
+
+        QNodeDefinitionBuilder oldChildDef1 = new QNodeDefinitionBuilder();
+        oldChildDef1.setRequiredPrimaryTypes(new Name[]{ NameConstants.NT_FOLDER });
+        oldChildDef1.setName(CHILD_NAME);
+        oldChildDef1.setDeclaringNodeType(oldDef.getName());
+
+        QNodeDefinitionBuilder oldChildDef2 = new QNodeDefinitionBuilder();
+        oldChildDef2.setRequiredPrimaryTypes(new Name[]{ NameConstants.NT_FILE });
+        oldChildDef2.setName(CHILD_NAME);
+        oldChildDef2.setDeclaringNodeType(oldDef.getName());
+
+        oldDef.setChildNodeDefs(new QNodeDefinition[] { oldChildDef1.build(), oldChildDef2.build() });
+
+        // new node type definition
+        QNodeTypeDefinitionBuilder newDef = new QNodeTypeDefinitionBuilder();
+        newDef.setName(NODE_TYPE1);
+        newDef.setSupertypes(new Name[] { NameConstants.NT_BASE });
+
+        QNodeDefinitionBuilder newChildDef1 = new QNodeDefinitionBuilder();
+        newChildDef1.setRequiredPrimaryTypes(new Name[]{ NameConstants.NT_FOLDER });
+        newChildDef1.setName(CHILD_NAME);
+        newChildDef1.setDeclaringNodeType(oldDef.getName());
+
+        QNodeDefinitionBuilder newChildDef2 = new QNodeDefinitionBuilder();
+        newChildDef2.setRequiredPrimaryTypes(new Name[]{ NameConstants.NT_FILE });
+        newChildDef2.setName(CHILD_NAME);
+        newChildDef2.setDeclaringNodeType(oldDef.getName());
+
+        QNodeDefinitionBuilder newChildDef3 = new QNodeDefinitionBuilder();
+        newChildDef3.setRequiredPrimaryTypes(new Name[]{ NameConstants.NT_RESOURCE });
+        newChildDef3.setName(CHILD_NAME);
+        newChildDef3.setDeclaringNodeType(oldDef.getName());
+
+        newDef.setChildNodeDefs(new QNodeDefinition[] { newChildDef1.build(), newChildDef2.build(), newChildDef3.build() });
+
+        // from old to new is trivial
+        NodeTypeDefDiff nodeTypeDefDiff = NodeTypeDefDiff.create(oldDef.build(), newDef.build());
+        assertTrue(nodeTypeDefDiff.isTrivial());
+
+        // .. but the reverse is not
+        nodeTypeDefDiff = NodeTypeDefDiff.create(newDef.build(), oldDef.build());
+        assertTrue(nodeTypeDefDiff.isMajor());
+
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/TestAll.java b/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/TestAll.java
index 5870eb1..d74ea11 100644
--- a/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/TestAll.java
+++ b/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/nodetype/TestAll.java
@@ -32,6 +32,7 @@ public class TestAll extends TestCase {
     public static Test suite() {
         TestSuite suite = new TestSuite("org.apache.jackrabbit.spi.commons.nodetype tests");
 
+        suite.addTestSuite(NodeTypeDefDiffTest.class);
         suite.addTestSuite(NodeDefinitionTemplateImplTest.class);
         suite.addTestSuite(PropertyDefinitionTemplateImplTest.class);
 
diff --git a/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/query/xpath/QueryFormatTest.java b/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/query/xpath/QueryFormatTest.java
index 299f917..a07e8e2 100644
--- a/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/query/xpath/QueryFormatTest.java
+++ b/jackrabbit-spi-commons/src/test/java/org/apache/jackrabbit/spi/commons/query/xpath/QueryFormatTest.java
@@ -67,6 +67,14 @@ public class QueryFormatTest extends TestCase {
         checkStatement("//element(*, foo)[foo/*/@bar = 'bla']");
     }
 
+    public void testStarNameAtBeginningOfPredicate() throws Exception {
+        checkStatement("//element(*, foo)[*/*/@bar = 'bla']");
+    }
+
+    public void testChildStarName() throws Exception {
+        checkStatement("//programs//*[*/@sunday]");
+    }
+
     public void testRepSimilar() throws Exception {
         checkStatement("//element(*, foo)[rep:similar(foo, '/some/path')]");
     }
diff --git a/jackrabbit-spi/pom.xml b/jackrabbit-spi/pom.xml
index 66f2f8d..b8d2268 100644
--- a/jackrabbit-spi/pom.xml
+++ b/jackrabbit-spi/pom.xml
@@ -26,7 +26,7 @@
   <parent>
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>jackrabbit-parent</artifactId>
-    <version>2.3.6</version>
+    <version>2.10.1</version>
     <relativePath>../jackrabbit-parent/pom.xml</relativePath>
   </parent>
   <artifactId>jackrabbit-spi</artifactId>
diff --git a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/Batch.java b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/Batch.java
index 859c63c..f5ab4b4 100644
--- a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/Batch.java
+++ b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/Batch.java
@@ -281,4 +281,13 @@ public interface Batch {
      * @see javax.jcr.Session#move(String, String)
      */
     public void move(NodeId srcNodeId, NodeId destParentNodeId, Name destName) throws RepositoryException;
+
+    /**
+     * Add a new content tree to the persistent layer.
+     *
+     * @param parentId
+     * @param contentTree
+     * @throws RepositoryException
+     */
+    public void setTree(NodeId parentId, Tree contentTree) throws RepositoryException;
 }
diff --git a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/ItemId.java b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/ItemId.java
index db141fa..9ed4537 100644
--- a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/ItemId.java
+++ b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/ItemId.java
@@ -43,7 +43,7 @@ package org.apache.jackrabbit.spi;
  *   <code>ItemId</code>.
  *   </td></tr>
  * </table>
- * <p/>
+ * <p>
  * Two <code>ItemId</code>s should be considered equal if both the unique part
  * and the path part are equal AND if they denote the same
  * {@link #denotesNode() type} of <code>ItemId</code>.
diff --git a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/Name.java b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/Name.java
index f2b4525..9a8b023 100644
--- a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/Name.java
+++ b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/Name.java
@@ -22,12 +22,12 @@ import java.io.Serializable;
  * A <code>Name</code> is a combination of a namespace URI and a local part.
  * Instances of this class are used to internally represent the names of JCR
  * content items and other objects within a content repository.
- * <p/>
+ * <p>
  * A <code>Name</code> is immutable once created.
- * <p/>
+ * <p>
  * The String representation of a <code>Name</code> object must be in the
  * format "<code>{namespaceURI}localPart</code>".
- * <p/>
+ * <p>
  * An implementation of the <code>Name</code> interface must implement the
  * {@link Object#equals(Object)} method such that two Name objects are equal if
  * both the namespace URI and the local part are equal.
diff --git a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/NameFactory.java b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/NameFactory.java
index c8460d0..1e85888 100644
--- a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/NameFactory.java
+++ b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/NameFactory.java
@@ -36,7 +36,7 @@ public interface NameFactory {
      * Returns a <code>Name</code> holding the value of the specified
      * string. The string must be in the format returned by the
      * <code>Name.toString()</code> method, i.e.
-     * <p/>
+     * <p>
      * <code><b>{</b>namespaceURI<b>}</b>localName</code>
      *
      * @param nameString a <code>String</code> containing the <code>Name</code>
diff --git a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/Path.java b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/Path.java
index 6644789..a7a5297 100644
--- a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/Path.java
+++ b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/Path.java
@@ -230,7 +230,7 @@ public interface Path extends Serializable {
     /**
      * Tests whether this path is normalized, i.e. whether it does not
      * contain redundant elements such as "." and "..".
-     * <p/>
+     * <p>
      * Note that a normalized path can still contain ".." elements if they are
      * not redundant, e.g. "../../a/b/c" would be a normalized relative path,
      * whereas "../a/../../a/b/c" wouldn't (although they're semantically
@@ -243,7 +243,7 @@ public interface Path extends Serializable {
 
     /**
      * Returns the normalized path representation of this path.
-     * <p/>
+     * <p>
      * If the path cannot be normalized (e.g. if an absolute path is normalized
      * that would result in a 'negative' path) a RepositoryException is thrown.
      *
@@ -255,7 +255,7 @@ public interface Path extends Serializable {
 
     /**
      * Returns the canonical path representation of this path.
-     * <p/>
+     * <p>
      * If the path is relative or cannot be normalized a RepositoryException
      * is thrown.
      *
@@ -301,7 +301,7 @@ public interface Path extends Serializable {
     /**
      * Normalizes this path and returns the ancestor path of the specified
      * relative degree.
-     * <p/>
+     * <p>
      * An ancestor of relative degree <i>x</i> is the path that is <i>x</i>
      * levels up along the path.
      * <ul>
@@ -311,7 +311,7 @@ public interface Path extends Serializable {
      * <li>And so on to <i>degree</i> = <i>n</i>, where <i>n</i> is the depth
      * of this path, which returns the root path.
      * </ul>
-     * <p/>
+     * <p>
      * If this path is relative the implementation may not be able to determine
      * if the ancestor at <code>degree</code> exists. Such an implementation
      * should properly build the ancestor (i.e. parent of .. is ../..) and
@@ -347,7 +347,7 @@ public interface Path extends Serializable {
      * Returns the length of this path, i.e. the number of its elements.
      * Note that the root element "/" counts as a separate element, e.g.
      * the length of "/a/b/c" is 4 whereas the length of "a/b/c" is 3.
-     * <p/>
+     * <p>
      * Also note that the special elements "." and ".." are not treated
      * specially, e.g. both "/a/./.." and "/a/b/c" have a length of 4
      * but this value does not necessarily reflect the true hierarchy level as
@@ -365,7 +365,7 @@ public interface Path extends Serializable {
      * this path is an absolute or a relative path. The depth also takes '.'
      * and '..' elements into account. The depth of the root path, an
      * identifier and the current path must be 0.
-     * <p/>
+     * <p>
      * Note that the returned value might be negative if this path is not
      * canonical, e.g. the depth of "../../a" is -1.
      *
@@ -486,14 +486,14 @@ public interface Path extends Serializable {
      * Object representation of a single JCR path element. An <code>Element</code>
      * object contains the <code>Name</code> and optional index of a single
      * JCR path element.
-     * <p/>
+     * <p>
      * Once created, a <code>Element</code> object must be immutable.
-     * <p/>
+     * <p>
      * The String presentation of an <code>Element</code> must be in the format
      * "<code>{namespaceURI}localPart</code>" or
      * "<code>{namespaceURI}localPart[index]</code>" case of an index greater
      * than {@link Path#INDEX_DEFAULT}.
-     * <p/>
+     * <p>
      * Note, that the implementation must implement the equals method such, that
      * two <code>Element</code> objects having equals <code>Name</code>s and the
      * same normalized index must be equal.
diff --git a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/PathFactory.java b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/PathFactory.java
index e974d00..8a78996 100644
--- a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/PathFactory.java
+++ b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/PathFactory.java
@@ -131,7 +131,7 @@ public interface PathFactory {
     /**
      * Creates a path element from the given <code>name</code>.
      * The created path element does not contain an explicit index.
-     * <p/>
+     * <p>
      * If the specified name denotes a <i>special</i> path element (either
      * {@link PathFactory#getParentElement()}, {@link PathFactory#getCurrentElement()} or
      * {@link PathFactory#getRootElement()}) then the associated constant is returned.
@@ -145,7 +145,7 @@ public interface PathFactory {
     /**
      * Same as {@link #createElement(Name)} except that an explicit index can be
      * specified.
-     * <p/>
+     * <p>
      * Note that an IllegalArgumentException will be thrown if the specified
      * name denotes a <i>special</i> path element (either
      * {@link PathFactory#getParentElement()}, {@link PathFactory#getCurrentElement()} or
diff --git a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/QNodeTypeDefinition.java b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/QNodeTypeDefinition.java
index 91ced07..1c1f8b5 100644
--- a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/QNodeTypeDefinition.java
+++ b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/QNodeTypeDefinition.java
@@ -115,7 +115,7 @@ public interface QNodeTypeDefinition {
      * referenced by <i>this</i> node type definition (e.g. as supertypes, as
      * required/default primary types in child node definitions, as REFERENCE
      * value constraints in property definitions).
-     * <p/>
+     * <p>
      * Note that self-references (e.g. a child node definition that specifies
      * the declaring node type as the default primary type) are not considered
      * dependencies.
diff --git a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/QValueConstraint.java b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/QValueConstraint.java
index f828c1e..6f1448c 100644
--- a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/QValueConstraint.java
+++ b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/QValueConstraint.java
@@ -47,7 +47,7 @@ public interface QValueConstraint {
      * For constraints that are not namespace prefix mapping sensitive this
      * method returns the same defined in
      * <code>{@link PropertyDefinition#getValueConstraints()}</code>.
-     * <p/>
+     * <p>
      * Those that are namespace prefix mapping sensitive (e.g.
      * <code>NameConstraint</code>, <code>PathConstraint</code> and
      * <code>ReferenceConstraint</code>) return an internal string.
diff --git a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/QueryInfo.java b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/QueryInfo.java
index 55d7a0c..fe42d24 100644
--- a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/QueryInfo.java
+++ b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/QueryInfo.java
@@ -17,6 +17,8 @@
 package org.apache.jackrabbit.spi;
 
 import javax.jcr.RangeIterator;
+import javax.jcr.RepositoryException;
+
 import java.util.Map;
 
 /**
@@ -46,9 +48,9 @@ public interface QueryInfo {
     public String[] getColumnNames();
 
     /**
-     * @return an array of <code>Name</code>s representing the selector names of
+     * @return an array of <code>String</code>s representing the selector names of
      *         the query result.
      * @see javax.jcr.query.QueryResult#getSelectorNames()
      */
-    public Name[] getSelectorNames();
+    public String[] getSelectorNames();
 }
diff --git a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/QueryResultRow.java b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/QueryResultRow.java
index 471758a..1750814 100644
--- a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/QueryResultRow.java
+++ b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/QueryResultRow.java
@@ -35,7 +35,7 @@ public interface QueryResultRow {
      * @see javax.jcr.query.Row#getNode()
      * @see javax.jcr.query.Row#getNode(String)
      */
-    public NodeId getNodeId(Name selectorName);
+    public NodeId getNodeId(String selectorName);
 
     /**
      * Returns score for the given <code>selectorName</code> of this result row.
@@ -44,7 +44,7 @@ public interface QueryResultRow {
      *                     default selector.
      * @return score for the given selector in this result row.
      */
-    public double getScore(Name selectorName);
+    public double getScore(String selectorName);
 
     /**
      * Returns an array of <code>QValue</code>s.
diff --git a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/RepositoryService.java b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/RepositoryService.java
index 88aa23f..11a208b 100644
--- a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/RepositoryService.java
+++ b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/RepositoryService.java
@@ -218,6 +218,37 @@ public interface RepositoryService {
      */
     public boolean isGranted(SessionInfo sessionInfo, ItemId itemId, String[] actions) throws RepositoryException;
 
+    /**
+     * TODO
+     *
+     *
+     * @param sessionInfo
+     * @return
+     * @throws RepositoryException
+     */
+    public PrivilegeDefinition[] getPrivilegeDefinitions(SessionInfo sessionInfo) throws RepositoryException;
+
+    /**
+     * TODO
+     * 
+     *
+     * @param sessionInfo
+     * @param id
+     * @return
+     * @throws RepositoryException
+     */
+    public Name[] getPrivilegeNames(SessionInfo sessionInfo, NodeId id) throws RepositoryException;
+    
+    /**
+     * TODO
+     *
+     * @param sessionInfo
+     * @param nodeId
+     * @return
+     * @throws RepositoryException
+     */
+    public PrivilegeDefinition[] getSupportedPrivileges(SessionInfo sessionInfo, NodeId nodeId) throws RepositoryException;
+
     //------------------------------------------------------< Reading items >---
     /**
      * Returns the <code>QNodeDefinition</code> for the <code>Node</code>
@@ -392,6 +423,18 @@ public interface RepositoryService {
      */
     public void submit(Batch batch) throws PathNotFoundException, ItemNotFoundException, NoSuchNodeTypeException, ValueFormatException, VersionException, LockException, ConstraintViolationException, AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException;
 
+    /**
+     * Creates a new {@code Tree} that can be populated and later on be applied
+     * to the specified {@code Batch} by calling {@code #setTree}.
+     *
+     * @param nodeName
+     * @param primaryTypeName
+     * @param uniqueId
+     * @return a new {@code Tree} instance.
+     * @throws RepositoryException
+     */
+    public Tree createTree(SessionInfo sessionInfo, Batch batch, Name nodeName, Name primaryTypeName, String uniqueId) throws RepositoryException;
+
     //-------------------------------------------------------------< Import >---
     /**
      * Imports the data present in the given <code>InputStream</code> into the
@@ -1015,14 +1058,14 @@ public interface RepositoryService {
      * events that go through the passed subscription and have been generated
      * after this method call must be filtered using the passed
      * <code>filters</code>.
-     * <p/>
+     * <p>
      * An implementation is required to accept at least event filter instances
      * created by {@link RepositoryService#createEventFilter}. Optionally an
      * implementation may also support event filters instanciated by the client
      * itself. An implementation may require special deployment in that case,
      * e.g. to make the event filter implementation class available to the
      * repository server.
-     * <p/>
+     * <p>
      * <b>Note on thread-safety:</b> it is permissible to call this methods
      * while another thread is blocked in calling {@link
      * RepositoryService#getEvents(Subscription, long)} using the same
@@ -1047,11 +1090,11 @@ public interface RepositoryService {
     /**
      * Retrieves the events that occurred since the last call to this method for
      * the passed subscription.
-     * <p/>
+     * <p>
      * Note, that an SPI implementation may support observation even if the
      * corresponding {@link javax.jcr.Repository#OPTION_OBSERVATION_SUPPORTED
      * repository descriptor} does return 'false'.
-     * <p/>
+     * <p>
      * An implementation should un-block a calling thread and let it return if
      * the associated subscription is disposed by another thread.
      *
@@ -1093,7 +1136,7 @@ public interface RepositoryService {
 
     /**
      * Indicates that the passed subscription is no longer needed.
-     * <p/>
+     * <p>
      * <b>Note on thread-safety:</b> it is permissible to call this methods
      * while another thread is blocked in calling {@link
      * RepositoryService#getEvents(Subscription, long)} using the same
diff --git a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/SessionInfo.java b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/SessionInfo.java
index 98401f8..3e8ba53 100644
--- a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/SessionInfo.java
+++ b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/SessionInfo.java
@@ -66,7 +66,6 @@ public interface SessionInfo {
      * lock identified by the given token.
      *
      * @param lockToken to be added.
-     * @param lockToken
      * @throws UnsupportedRepositoryOperationException If locking is not supported.
      * @throws LockException If the token cannot be added.
      * @throws RepositoryException If another error occurs.
diff --git a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/Tree.java b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/Tree.java
new file mode 100644
index 0000000..6ce3a48
--- /dev/null
+++ b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/Tree.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.spi;
+
+import javax.jcr.RepositoryException;
+
+/**
+ * Interface for building a hierarchy of JCR items on
+ * the SPI layer.
+ */
+public interface Tree {
+    
+    public Name getName();
+
+    public Name getPrimaryTypeName();
+
+    public String getUniqueId();
+
+    public void addProperty(NodeId parentId, Name propertyName, int propertyType, QValue value) throws RepositoryException;
+
+    public void addProperty(NodeId parentId, Name propertyName, int propertyType, QValue[] values) throws RepositoryException;
+
+    public Tree addChild(Name childName, Name primaryTypeName, String uniqueId);
+}
diff --git a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/package-info.java b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/package-info.java
index 72aa8ba..4b3caaf 100644
--- a/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/package-info.java
+++ b/jackrabbit-spi/src/main/java/org/apache/jackrabbit/spi/package-info.java
@@ -53,5 +53,5 @@
  * <code>RepositoryService.getEvents()</code> will always throw an
  * {@link javax.jcr.UnsupportedRepositoryOperationException}.
  */
- at aQute.bnd.annotation.Version("2.4.0")
+ at aQute.bnd.annotation.Version("3.0.0")
 package org.apache.jackrabbit.spi;
diff --git a/jackrabbit-spi/src/test/java/org/apache/jackrabbit/spi/Helper.java b/jackrabbit-spi/src/test/java/org/apache/jackrabbit/spi/Helper.java
index 5c9403b..11c4044 100644
--- a/jackrabbit-spi/src/test/java/org/apache/jackrabbit/spi/Helper.java
+++ b/jackrabbit-spi/src/test/java/org/apache/jackrabbit/spi/Helper.java
@@ -72,7 +72,7 @@ public class Helper {
      * Returns the value of the configuration property with specified
      * <code>name</code>. If the property does not exist <code>null</code> is
      * returned.
-     * <p/>
+     * <p>
      * Configuration properties are defined in the file:
      * <code>repositoryStubImpl.properties</code>.
      *
diff --git a/jackrabbit-spi2dav/pom.xml b/jackrabbit-spi2dav/pom.xml
index 6e64490..50d7516 100644
--- a/jackrabbit-spi2dav/pom.xml
+++ b/jackrabbit-spi2dav/pom.xml
@@ -26,7 +26,7 @@
   <parent>
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>jackrabbit-parent</artifactId>
-    <version>2.3.6</version>
+    <version>2.10.1</version>
     <relativePath>../jackrabbit-parent/pom.xml</relativePath>
   </parent>
   <artifactId>jackrabbit-spi2dav</artifactId>
@@ -75,17 +75,8 @@
                   org.apache.jackrabbit.test.api.lock.LockManagerTest#testLockTransfer2
                   <!-- JCR-2533 : missing impl of checkQueryStatement -->
                   org.apache.jackrabbit.test.api.query.CreateQueryTest#testUnknownQueryLanguage
-                  <!-- JCR-2543 : query offset -->
-                  org.apache.jackrabbit.test.api.query.SetOffsetTest#testSetOffset
                   <!-- JCR-2533 : missing impl of checkQueryStatement -->
                   org.apache.jackrabbit.test.api.query.qom.BindVariableValueTest
-                  <!-- JCR-2535 : Row.getPath() called with multiple selectors (server-side) -->
-                  org.apache.jackrabbit.test.api.query.qom.ChildNodeJoinConditionTest
-                  org.apache.jackrabbit.test.api.query.qom.ColumnTest#testMultiColumn
-                  org.apache.jackrabbit.test.api.query.qom.DescendantNodeJoinConditionTest
-                  org.apache.jackrabbit.test.api.query.qom.EquiJoinConditionTest
-                  org.apache.jackrabbit.test.api.query.qom.OrderingTest#testMultipleSelectors
-                  org.apache.jackrabbit.test.api.query.qom.SameNodeJoinConditionTest
                   <!-- JCR-2112 : simple versioning not implemented -->
                   org.apache.jackrabbit.test.api.version.simple
                   <!-- JCR-2104 : activities and configuration -->
@@ -127,23 +118,23 @@
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-spi</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <classifier />
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-spi-commons</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-commons</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-webdav</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>javax.jcr</groupId>
@@ -165,20 +156,20 @@
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-tests</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr2spi</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <classifier />
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr2spi</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <classifier>tests</classifier>
       <scope>test</scope>
     </dependency>
@@ -186,7 +177,7 @@
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-spi</artifactId>
       <classifier>tests</classifier>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/BatchUtils.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/BatchUtils.java
new file mode 100644
index 0000000..9c3c236
--- /dev/null
+++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/BatchUtils.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.spi2dav;
+
+import javax.jcr.NamespaceException;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.webdav.xml.DomUtil;
+import org.apache.jackrabbit.webdav.xml.Namespace;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+final class BatchUtils {
+
+    /**
+     * The XML elements and attributes used in serialization
+     */
+    private static final Namespace SV_NAMESPACE = Namespace.getNamespace(Name.NS_SV_PREFIX, Name.NS_SV_URI);
+    private static final String NODE_ELEMENT = "node";
+    private static final String PROPERTY_ELEMENT = "property";
+    private static final String VALUE_ELEMENT = "value";
+    private static final String NAME_ATTRIBUTE = "name";
+    private static final String TYPE_ATTRIBUTE = "type";
+
+    private BatchUtils() {};
+
+    static Element createNodeElement(Node parent, Name nodeName, Name primaryTypeName, String uniqueId, NamePathResolver resolver) throws NamespaceException {
+        Element nodeElement = DomUtil.addChildElement(parent, NODE_ELEMENT, SV_NAMESPACE);
+        String nameAttr = resolver.getJCRName(nodeName);
+        DomUtil.setAttribute(nodeElement, NAME_ATTRIBUTE, SV_NAMESPACE, nameAttr);
+
+        // nodetype must never be null
+        Element propElement = DomUtil.addChildElement(nodeElement, PROPERTY_ELEMENT, SV_NAMESPACE);
+        String name = resolver.getJCRName(NameConstants.JCR_PRIMARYTYPE);
+        DomUtil.setAttribute(propElement, NAME_ATTRIBUTE, SV_NAMESPACE, name);
+        DomUtil.setAttribute(propElement, TYPE_ATTRIBUTE, SV_NAMESPACE, PropertyType.nameFromValue(PropertyType.NAME));
+        name = resolver.getJCRName(primaryTypeName);
+        DomUtil.addChildElement(propElement, VALUE_ELEMENT, SV_NAMESPACE, name);
+        // optional uuid
+        if (uniqueId != null) {
+            propElement = DomUtil.addChildElement(nodeElement, PROPERTY_ELEMENT, SV_NAMESPACE);
+            name = resolver.getJCRName(NameConstants.JCR_UUID);
+            DomUtil.setAttribute(propElement, NAME_ATTRIBUTE, SV_NAMESPACE, name);
+            DomUtil.setAttribute(propElement, TYPE_ATTRIBUTE, SV_NAMESPACE, PropertyType.nameFromValue(PropertyType.STRING));
+            DomUtil.addChildElement(propElement, VALUE_ELEMENT, SV_NAMESPACE, uniqueId);
+        }
+        return nodeElement;
+    }
+
+    static void importProperty(Element nodeElement, Name propertyName, int type, QValue[] values, NamePathResolver resolver) throws RepositoryException {
+        Element propElement = DomUtil.addChildElement(nodeElement, PROPERTY_ELEMENT, SV_NAMESPACE);
+        DomUtil.setAttribute(propElement, NAME_ATTRIBUTE, SV_NAMESPACE, resolver.getJCRName(propertyName));
+        DomUtil.setAttribute(propElement, TYPE_ATTRIBUTE, SV_NAMESPACE, PropertyType.nameFromValue(type));
+
+        // build all the values.
+        for (QValue value : values) {
+            DomUtil.addChildElement(propElement, VALUE_ELEMENT, SV_NAMESPACE, value.getString());
+        }
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/DocumentTree.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/DocumentTree.java
new file mode 100644
index 0000000..7a698b3
--- /dev/null
+++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/DocumentTree.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.spi2dav;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.jcr.RepositoryException;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.spi.Tree;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+import org.apache.jackrabbit.spi.commons.tree.AbstractTree;
+import org.apache.jackrabbit.webdav.xml.DomUtil;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+class DocumentTree extends AbstractTree {
+
+    private final List<Property> properties = new ArrayList<Property>();
+
+    protected DocumentTree(Name nodeName, Name ntName, String uniqueId, NamePathResolver resolver) {
+        super(nodeName, ntName, uniqueId, resolver);
+    }
+
+    //-------------------------------------------------------< AbstractTree >---
+    @Override
+    protected Tree createChild(Name name, Name primaryTypeName, String uniqueId) {
+        return new DocumentTree(name, primaryTypeName, uniqueId, getResolver());
+    }
+
+    //---------------------------------------------------------------< Tree >---
+    @Override
+    public void addProperty(NodeId parentId, Name propertyName, int propertyType, QValue value) throws RepositoryException {
+        addProperty(parentId, propertyName, propertyType, new QValue[]{value});
+    }
+
+    @Override
+    public void addProperty(NodeId parentId, Name propertyName, int propertyType, QValue[] values) throws RepositoryException {
+        properties.add(new Property(propertyName, propertyType, values));
+    }
+
+    //--------------------------------------------------------------------------
+    Document toDocument() throws RepositoryException {
+        try {
+            Document body = DomUtil.createDocument();
+            buildNodeInfo(body, this);
+            return body;
+        } catch (ParserConfigurationException e) {
+            throw new RepositoryException(e);
+        }
+    }
+
+    //--------------------------------------------------------------------------
+    private void buildNodeInfo(Node parent, DocumentTree tree) throws RepositoryException {
+        Element node = BatchUtils.createNodeElement(parent, tree.getName(), tree.getPrimaryTypeName(), tree.getUniqueId(), getResolver());
+        for (Property prop : properties) {
+            BatchUtils.importProperty(node, prop.name, prop.type, prop.values, getResolver());
+        }
+        for (Tree child : tree.getChildren()) {
+            buildNodeInfo(node, (DocumentTree) child);
+        }
+    }
+
+    private final static class Property {
+
+        private final Name name;
+        private final int type;
+        private final QValue[] values;
+
+        private Property(Name name, int type, QValue[] values) {
+            this.name = name;
+            this.type = type;
+            this.values = values;
+        }
+    }
+}
diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventImpl.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventImpl.java
index 65d977a..820e21c 100644
--- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventImpl.java
+++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventImpl.java
@@ -61,11 +61,10 @@ public class EventImpl
             Element eventElement, NamePathResolver resolver, QValueFactory qvFactory) throws NamespaceException,
             IllegalNameException {
         super(getSpiEventType(eventType), eventPath, eventId, parentId, getNameSafe(
-                DomUtil.getChildTextTrim(eventElement, XML_EVENTPRIMARNODETYPE, NAMESPACE), resolver), getNames(
-                DomUtil.getChildren(eventElement, XML_EVENTMIXINNODETYPE, NAMESPACE), resolver), userId, DomUtil
-                .getChildTextTrim(eventElement, XML_EVENTUSERDATA, NAMESPACE), Long.parseLong(DomUtil.getChildTextTrim(
-                eventElement, XML_EVENTDATE, NAMESPACE)), getEventInfo(
-                DomUtil.getChildElement(eventElement, XML_EVENTINFO, NAMESPACE), resolver, qvFactory));
+                DomUtil.getChildTextTrim(eventElement, N_EVENTPRIMARYNODETYPE), resolver), getNames(
+                DomUtil.getChildren(eventElement, N_EVENTMIXINNODETYPE), resolver), userId, DomUtil.getChildTextTrim(
+                eventElement, N_EVENTUSERDATA), Long.parseLong(DomUtil.getChildTextTrim(eventElement, N_EVENTDATE)),
+                getEventInfo(DomUtil.getChildElement(eventElement, N_EVENTINFO), resolver, qvFactory));
     }
 
     //--------------------------------------------------------------------------
@@ -115,7 +114,7 @@ public class EventImpl
                 }
                 info.put(n, qv);
             } catch (RepositoryException e) {
-                log.error("Internal Error: ", e.getMessage());
+                log.error("Internal Error: {}", e.getMessage());
             }
         }
         return info;
@@ -157,4 +156,4 @@ public class EventImpl
 
         return results.toArray(new Name[results.size()]);
     }
-}
\ No newline at end of file
+}
diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/IdURICache.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/IdURICache.java
index 29d6772..a489fa7 100644
--- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/IdURICache.java
+++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/IdURICache.java
@@ -16,27 +16,41 @@
  */
 package org.apache.jackrabbit.spi2dav;
 
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.apache.jackrabbit.spi.ItemId;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.apache.jackrabbit.spi.ItemId;
-
-import java.util.Map;
-import java.util.HashMap;
 
 /**
  * <code>IdURICache</code>...
  */
 class IdURICache {
-
     private static Logger log = LoggerFactory.getLogger(IdURICache.class);
 
-    private final String workspaceUri;
+    /**
+     * @see <a href="https://issues.apache.org/jira/browse/JCR-3305">JCR-3305</a>: limit cache size
+     */
+    private static final int CACHESIZE = 10000;
 
-    private Map<ItemId, String> idToUriCache = new HashMap<ItemId, String>();
-    private Map<String, ItemId> uriToIdCache = new HashMap<String, ItemId>();
+    private final String workspaceUri;
+    private Map<ItemId, String> idToUriCache;
+    private Map<String, ItemId> uriToIdCache;
 
     IdURICache(String workspaceUri) {
         this.workspaceUri = workspaceUri;
+        idToUriCache = new LinkedHashMap<ItemId, String>(CACHESIZE, 1) {
+            @Override
+            protected boolean removeEldestEntry(Map.Entry<ItemId, String> eldest) {
+                return this.size() > CACHESIZE;
+            }
+        };
+        uriToIdCache = new LinkedHashMap<String, ItemId>(CACHESIZE, 1) {
+            @Override
+            protected boolean removeEldestEntry(Map.Entry<String, ItemId> eldest) {
+                return this.size() > CACHESIZE;
+            }
+        };
     }
 
     public ItemId getItemId(String uri) {
@@ -57,7 +71,7 @@ class IdURICache {
 
     public void add(String uri, ItemId itemId) {
         if (!uri.startsWith(workspaceUri)) {
-            throw new IllegalArgumentException("Workspace missmatch.");
+            throw new IllegalArgumentException("Workspace mismatch: '" + uri + "' not under '" + workspaceUri + "'");
         }
         String cleanUri = getCleanUri(uri);
         uriToIdCache.put(cleanUri, itemId);
diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/LockInfoImpl.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/LockInfoImpl.java
index 690ce35..c9a5469 100644
--- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/LockInfoImpl.java
+++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/LockInfoImpl.java
@@ -15,12 +15,14 @@
 */
 package org.apache.jackrabbit.spi2dav;
 
-import org.apache.jackrabbit.webdav.lock.ActiveLock;
-import org.apache.jackrabbit.webdav.DavConstants;
+import java.util.Set;
+
 import org.apache.jackrabbit.spi.LockInfo;
 import org.apache.jackrabbit.spi.NodeId;
-import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.webdav.DavConstants;
+import org.apache.jackrabbit.webdav.lock.ActiveLock;
 import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * <code>LockInfoImpl</code>...
@@ -31,10 +33,12 @@ public class LockInfoImpl implements LockInfo {
 
     private final ActiveLock activeLock;
     private final NodeId nodeId;
+    private final Set<String> sessionLockTokens;
 
-    public LockInfoImpl(ActiveLock activeLock, NodeId nodeId) {
+    public LockInfoImpl(ActiveLock activeLock, NodeId nodeId, Set<String> sessionLockTokens) {
         this.activeLock = activeLock;
         this.nodeId = nodeId;
+        this.sessionLockTokens = sessionLockTokens;
     }
 
     ActiveLock getActiveLock() {
@@ -64,7 +68,12 @@ public class LockInfoImpl implements LockInfo {
     }
 
     public boolean isLockOwner() {
-        return activeLock.getToken() != null;
+        String lt = activeLock.getToken();
+        if (lt == null) {
+            return false;
+        } else {
+            return sessionLockTokens.contains(lt);
+        }
     }
 
     public NodeId getNodeId() {
diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/QueryInfoImpl.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/QueryInfoImpl.java
index 4431cca..53cd48f 100644
--- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/QueryInfoImpl.java
+++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/QueryInfoImpl.java
@@ -18,8 +18,6 @@ package org.apache.jackrabbit.spi2dav;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Set;
-import java.util.HashSet;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.ValueFactory;
@@ -29,26 +27,24 @@ import org.apache.jackrabbit.spi.QueryInfo;
 import org.apache.jackrabbit.spi.QValueFactory;
 import org.apache.jackrabbit.spi.QueryResultRow;
 import org.apache.jackrabbit.spi.IdFactory;
-import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.util.ISO9075;
 import org.apache.jackrabbit.webdav.MultiStatus;
 import org.apache.jackrabbit.webdav.MultiStatusResponse;
 import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
 import org.apache.jackrabbit.commons.iterator.RangeIteratorAdapter;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * <code>QueryInfoImpl</code>...
  */
 public class QueryInfoImpl implements QueryInfo {
 
-    /**
-     * Logger instance for this class.
-     */
-    private static final Logger log = LoggerFactory.getLogger(QueryInfoImpl.class);
+    private static final String COLUMNS = "Columns: ";
+
+    private static final String SELECTORS = "Selectors: ";
+
+    private final List<String> columnNames = new ArrayList<String>();
 
-    private final String[] columnNames;
+    private final List<String> selectorNames = new ArrayList<String>();
 
     private final List<QueryResultRow> results = new ArrayList<QueryResultRow>();
 
@@ -58,23 +54,45 @@ public class QueryInfoImpl implements QueryInfo {
         throws RepositoryException {
 
         String responseDescription = ms.getResponseDescription();
-        if (responseDescription != null) {
-            String[] cn = responseDescription.split(" ");
-            this.columnNames = new String[cn.length];
-            for (int i = 0; i < cn.length; i++) {
-                columnNames[i] = ISO9075.decode(cn[i]);
+        if (responseDescription == null) {
+            throw new RepositoryException(
+                    "Missing column infos: Unable to build QueryInfo object.");
+        }
+        if (responseDescription.startsWith(COLUMNS)) {
+            for (String line : responseDescription.split("\n")) {
+                if (line.startsWith(COLUMNS)) {
+                    decode(line.substring(COLUMNS.length()), columnNames);
+                } else if (line.startsWith(SELECTORS)) {
+                    decode(line.substring(SELECTORS.length()), selectorNames);
+                }
             }
         } else {
-            throw new RepositoryException("Missing column infos: Unable to build QueryInfo object.");
+            // Backwards compatibility with old servers that only provide
+            // the list of columns as the response description
+            decode(responseDescription, columnNames);
         }
 
         for (MultiStatusResponse response : ms.getResponses()) {
-            results.add(new QueryResultRowImpl(response, columnNames, resolver,
+            results.add(new QueryResultRowImpl(
+                    response, getColumnNames(), resolver,
                     qValueFactory, valueFactory, idFactory));
         }
     }
 
     /**
+     * Splits the given string at spaces and ISO9075-decodes the parts.
+     *
+     * @param string source string
+     * @param list where the decoded parts get added
+     */
+    private void decode(String string, List<String> list) {
+        String[] parts = string.split(" ");
+        for (int i = 0; i < parts.length; i++) {
+            list.add(ISO9075.decode(parts[i]));
+        }
+    }
+
+    /**
      * @see QueryInfo#getRows()
      */
     public RangeIterator getRows() {
@@ -85,27 +103,13 @@ public class QueryInfoImpl implements QueryInfo {
      * @see QueryInfo#getColumnNames()
      */
     public String[] getColumnNames() {
-        String[] names = new String[columnNames.length];
-        System.arraycopy(columnNames, 0, names, 0, columnNames.length);
-        return names;
+        return columnNames.toArray(new String[columnNames.size()]);
     }
 
     /**
      * @see QueryInfo#getSelectorNames()
      */
-    public Name[] getSelectorNames() {
-        if (results.isEmpty()) {
-            // TODO: this is not correct
-            return Name.EMPTY_ARRAY;
-        } else {
-            Set<Name> uniqueNames = new HashSet<Name>();
-            QueryResultRowImpl row = (QueryResultRowImpl) results.get(0);
-            for (Name n : row.getSelectorNames()) {
-                if (n != null) {
-                    uniqueNames.add(n);
-                }
-            }
-            return uniqueNames.toArray(new Name[uniqueNames.size()]);
-        }
+    public String[] getSelectorNames() {
+        return selectorNames.toArray(new String[selectorNames.size()]);
     }
 }
diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/QueryResultRowImpl.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/QueryResultRowImpl.java
index 1660fee..e83112d 100644
--- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/QueryResultRowImpl.java
+++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/QueryResultRowImpl.java
@@ -29,7 +29,6 @@ import org.apache.jackrabbit.commons.webdav.JcrRemotingConstants;
 import org.apache.jackrabbit.commons.webdav.QueryUtil;
 import org.apache.jackrabbit.spi.QueryResultRow;
 import org.apache.jackrabbit.spi.NodeId;
-import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.QValue;
 import org.apache.jackrabbit.spi.QValueFactory;
 import org.apache.jackrabbit.spi.IdFactory;
@@ -54,16 +53,14 @@ public class QueryResultRowImpl implements QueryResultRow {
 
     private static final DavPropertyName SEARCH_RESULT_PROPERTY = DavPropertyName.create(JcrRemotingConstants.JCR_QUERY_RESULT_LN, ItemResourceConstants.NAMESPACE);
 
-    private final Map<Name, NodeId> nodeIds = new HashMap<Name, NodeId>();
+    private final Map<String, NodeId> nodeIds = new HashMap<String, NodeId>();
 
-    private final Map<Name, Double> scores = new HashMap<Name, Double>();
+    private final Map<String, Double> scores = new HashMap<String, Double>();
 
     private final Map<String, QValue> qValues = new HashMap<String, QValue>();
 
     private final String[] columnNames;
 
-    private final Name[] selectorNames;
-
     public QueryResultRowImpl(MultiStatusResponse response,
                               String[] columnNames,
                               NamePathResolver resolver,
@@ -72,6 +69,7 @@ public class QueryResultRowImpl implements QueryResultRow {
                               IdFactory idFactory)
             throws RepositoryException {
         this.columnNames = columnNames;
+
         DavPropertySet okSet = response.getProperties(DavServletResponse.SC_OK);
 
         String jcrPath = resolver.getJCRName(NameConstants.JCR_PATH);
@@ -86,24 +84,22 @@ public class QueryResultRowImpl implements QueryResultRow {
         String[] names = colList.toArray(new String[colList.size()]);
         Value[] values = valList.toArray(new Value[valList.size()]);
 
-        this.selectorNames = new Name[selList.size()];
         for (int i = 0; i < values.length; i++) {
             try {
                 String selectorName = selList.get(i);
                 QValue v = (values[i] == null) ? null : ValueFormat.getQValue(values[i], resolver, qValueFactory);
-                this.selectorNames[i] = (selectorName == null) ? null : resolver.getQName(selectorName);
                 if (jcrScore.equals(names[i])) {
                     Double score = 0.0;
                     if (v != null) {
                         score = v.getDouble();
                     }
-                    scores.put(this.selectorNames[i], score);
+                    scores.put(selectorName, score);
                 } else if (jcrPath.equals(names[i])) {
                     NodeId id = null;
                     if (v != null) {
                         id = idFactory.createNodeId((String) null, v.getPath());
                     }
-                    nodeIds.put(this.selectorNames[i], id);
+                    nodeIds.put(selectorName, id);
                 }
                 qValues.put(names[i], v);
             } catch (RepositoryException e) {
@@ -113,26 +109,26 @@ public class QueryResultRowImpl implements QueryResultRow {
         }
     }
 
-    public NodeId getNodeId(Name selectorName) {
+    public NodeId getNodeId(String selectorName) {
+        if (selectorName == null && scores.size() == 1) {
+            return nodeIds.values().iterator().next();
+        }
+
         NodeId id = nodeIds.get(selectorName);
-        if (id == null) {
-            if (nodeIds.size() == 1) {
-                return nodeIds.values().iterator().next();
-            } else {
-                throw new IllegalArgumentException(selectorName + " is not a valid selectorName");
-            }
+        if (id == null && !nodeIds.containsKey(selectorName)) {
+            throw new IllegalArgumentException(selectorName + " is not a valid selectorName");
         }
         return id;
     }
 
-    public double getScore(Name selectorName) {
+    public double getScore(String selectorName) {
+        if (selectorName == null && scores.size() == 1) {
+            return scores.values().iterator().next();
+        }
+
         Double score = scores.get(selectorName);
-        if (score == null) {
-            if (scores.size() == 1) {
-                return scores.values().iterator().next();
-            } else {
-                throw new IllegalArgumentException(selectorName + " is not a valid selectorName");
-            }
+        if (score == null && !nodeIds.containsKey(selectorName)) {
+            throw new IllegalArgumentException(selectorName + " is not a valid selectorName");
         }
         return score;
     }
@@ -145,7 +141,4 @@ public class QueryResultRowImpl implements QueryResultRow {
         return values;
     }
 
-    Name[] getSelectorNames() {
-        return selectorNames;
-    }
 }
diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java
index 5352c5b..c56ca11 100644
--- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java
+++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java
@@ -22,6 +22,7 @@ import java.io.InputStreamReader;
 import java.io.Reader;
 import java.io.StringWriter;
 import java.io.UnsupportedEncodingException;
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -44,6 +45,7 @@ import javax.jcr.LoginException;
 import javax.jcr.MergeException;
 import javax.jcr.NamespaceException;
 import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.PathNotFoundException;
 import javax.jcr.PropertyType;
 import javax.jcr.RepositoryException;
 import javax.jcr.UnsupportedRepositoryOperationException;
@@ -98,6 +100,7 @@ import org.apache.jackrabbit.spi.NodeId;
 import org.apache.jackrabbit.spi.NodeInfo;
 import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.PathFactory;
+import org.apache.jackrabbit.spi.PrivilegeDefinition;
 import org.apache.jackrabbit.spi.PropertyId;
 import org.apache.jackrabbit.spi.PropertyInfo;
 import org.apache.jackrabbit.spi.QItemDefinition;
@@ -110,6 +113,7 @@ import org.apache.jackrabbit.spi.QueryInfo;
 import org.apache.jackrabbit.spi.RepositoryService;
 import org.apache.jackrabbit.spi.SessionInfo;
 import org.apache.jackrabbit.spi.Subscription;
+import org.apache.jackrabbit.spi.Tree;
 import org.apache.jackrabbit.spi.commons.ChildInfoImpl;
 import org.apache.jackrabbit.spi.commons.EventBundleImpl;
 import org.apache.jackrabbit.spi.commons.EventFilterImpl;
@@ -128,6 +132,7 @@ import org.apache.jackrabbit.spi.commons.name.NameConstants;
 import org.apache.jackrabbit.spi.commons.namespace.AbstractNamespaceResolver;
 import org.apache.jackrabbit.spi.commons.namespace.NamespaceResolver;
 import org.apache.jackrabbit.spi.commons.nodetype.compact.CompactNodeTypeDefWriter;
+import org.apache.jackrabbit.spi.commons.privilege.PrivilegeDefinitionImpl;
 import org.apache.jackrabbit.spi.commons.value.QValueValue;
 import org.apache.jackrabbit.spi.commons.value.ValueFactoryQImpl;
 import org.apache.jackrabbit.spi.commons.value.ValueFormat;
@@ -185,6 +190,8 @@ import org.apache.jackrabbit.webdav.search.SearchInfo;
 import org.apache.jackrabbit.webdav.security.CurrentUserPrivilegeSetProperty;
 import org.apache.jackrabbit.webdav.security.Privilege;
 import org.apache.jackrabbit.webdav.security.SecurityConstants;
+import org.apache.jackrabbit.webdav.security.SupportedPrivilege;
+import org.apache.jackrabbit.webdav.security.SupportedPrivilegeSetProperty;
 import org.apache.jackrabbit.webdav.transaction.TransactionConstants;
 import org.apache.jackrabbit.webdav.transaction.TransactionInfo;
 import org.apache.jackrabbit.webdav.version.DeltaVConstants;
@@ -195,7 +202,6 @@ import org.apache.jackrabbit.webdav.version.VersionControlledResource;
 import org.apache.jackrabbit.webdav.version.report.ReportInfo;
 import org.apache.jackrabbit.webdav.xml.DomUtil;
 import org.apache.jackrabbit.webdav.xml.ElementIterator;
-import org.apache.jackrabbit.webdav.xml.Namespace;
 import org.apache.jackrabbit.webdav.xml.XmlSerializable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -250,6 +256,9 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
     private boolean remoteServerProvidesNodeTypes = false;
     private boolean remoteServerProvidesNoLocalFlag = false;
 
+    /* DAV conformance levels */
+    private Set<String> remoteDavComplianceClasses = null;
+
     /**
      * Same as {@link #RepositoryServiceImpl(String, IdFactory, NameFactory, PathFactory, QValueFactory, int, int)}
      * using {@link ItemInfoCacheImpl#DEFAULT_CACHE_SIZE)} as size for the item
@@ -320,7 +329,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
         this.itemInfoCacheSize = itemInfoCacheSize;
 
         try {
-            URI repositoryUri = new URI((uri.endsWith("/")) ? uri : uri+"/", true);
+            URI repositoryUri = computeRepositoryUri(uri);
             hostConfig = new HostConfiguration();
             hostConfig.setHost(repositoryUri);
 
@@ -339,6 +348,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
         if (maximumHttpConnections > 0) {
             HttpConnectionManagerParams connectionParams = connectionManager.getParams();
             connectionParams.setDefaultMaxConnectionsPerHost(maximumHttpConnections);
+            connectionParams.setMaxTotalConnections(maximumHttpConnections);
         }
 
         // This configuration of the clients cache assumes that the level of
@@ -360,6 +370,20 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
         }
     }
 
+    /**
+     * Resolve the given URI against a base URI (usually the request URI of an HTTP request)
+     */
+    private static String resolve(String baseUri, String relUri) throws RepositoryException {
+        try {
+            java.net.URI base = new java.net.URI(baseUri);
+            java.net.URI rel = new java.net.URI(relUri);
+            return base.resolve(rel).toString();
+        }
+        catch (URISyntaxException ex) {
+            throw new RepositoryException(ex);
+        }
+    }
+
     private static void checkSubscription(Subscription subscription) throws RepositoryException {
         if (!(subscription instanceof EventSubscriptionImpl)) {
             throw new RepositoryException("Unknown Subscription implementation.");
@@ -374,9 +398,10 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
     protected static void initMethod(HttpMethod method, SessionInfo sessionInfo, boolean addIfHeader) throws RepositoryException {
         if (addIfHeader) {
             checkSessionInfo(sessionInfo);
-            String[] locktokens = ((SessionInfoImpl) sessionInfo).getAllLockTokens();
+            Set<String> allLockTokens = ((SessionInfoImpl) sessionInfo).getAllLockTokens();
             // TODO: ev. build tagged if header
-            if (locktokens != null && locktokens.length > 0) {
+            if (!allLockTokens.isEmpty()) {
+                String[] locktokens = allLockTokens.toArray(new String[allLockTokens.size()]);
                 IfHeader ifH = new IfHeader(locktokens);
                 method.setRequestHeader(ifH.getHeaderName(), ifH.getHeaderValue());
             }
@@ -409,14 +434,14 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
 
             String sessionIdentifier = ((SessionInfoImpl) sessionInfo)
                     .getSessionIdentifier();
-            linkHeaderField.append("<" + sessionIdentifier + ">; rel=\""
-                    + JcrRemotingConstants.RELATION_REMOTE_SESSION_ID + "\"");
+            linkHeaderField.append("<").append(sessionIdentifier).append(">; rel=\"")
+                    .append(JcrRemotingConstants.RELATION_REMOTE_SESSION_ID).append("\"");
 
             String userdata = ((SessionInfoImpl) sessionInfo).getUserData();
             if (userdata != null && ! isReadAccess) {
                 String escaped = Text.escape(userdata);
-                linkHeaderField.append((", <data:," + escaped + ">; rel=\""
-                        + JcrRemotingConstants.RELATION_USER_DATA + "\""));
+                linkHeaderField.append(", <data:,").append(escaped).append(">; rel=\"")
+                    .append(JcrRemotingConstants.RELATION_USER_DATA).append("\"");
             }
 
             method.addRequestHeader("Link", linkHeaderField.toString());
@@ -432,11 +457,15 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
     }
 
     private static boolean isSameResource(String requestURI, MultiStatusResponse response) {
-        String href = response.getHref();
-        if (href.endsWith("/") && !requestURI.endsWith("/")) {
-            href = href.substring(0, href.length() - 1);
+        try {
+            String href = resolve(requestURI, response.getHref());
+            if (href.endsWith("/") && !requestURI.endsWith("/")) {
+                href = href.substring(0, href.length() - 1);
+            }
+            return requestURI.equals(href);
+        } catch (RepositoryException e) {
+            return false;
         }
-        return requestURI.equals(href);
     }
 
     private String saveGetIdString(ItemId id, SessionInfo sessionInfo) {
@@ -552,7 +581,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
         return parentUri + Text.escape(resolver.getJCRName(childName));
     }
 
-    private NodeId getParentId(DavPropertySet propSet, SessionInfo sessionInfo)
+    private NodeId getParentId(String baseUri, DavPropertySet propSet, SessionInfo sessionInfo)
         throws RepositoryException {
         NodeId parentId = null;
         DavProperty<?> p = propSet.get(JcrRemotingConstants.JCR_PARENT_LN, ItemResourceConstants.NAMESPACE);
@@ -560,7 +589,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
             HrefProperty parentProp = new HrefProperty(p);
             String parentHref = parentProp.getHrefs().get(0);
             if (parentHref != null && parentHref.length() > 0) {
-                parentId = uriResolver.getNodeId(parentHref, sessionInfo);
+                parentId = uriResolver.getNodeId(resolve(baseUri, parentHref), sessionInfo);
             }
         }
         return parentId;
@@ -843,7 +872,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
     public boolean isGranted(SessionInfo sessionInfo, ItemId itemId, String[] actions) throws RepositoryException {
         ReportMethod method = null;
         try {
-            String uri = getItemUri(itemId, sessionInfo);
+            String uri = obtainAbsolutePathFromUri(getItemUri(itemId, sessionInfo));
             ReportInfo reportInfo = new ReportInfo(JcrRemotingConstants.REPORT_PRIVILEGES, ItemResourceConstants.NAMESPACE);
             reportInfo.setContentElement(DomUtil.hrefToXml(uri, DomUtil.createDocument()));
 
@@ -885,6 +914,119 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
         }
     }
 
+    @Override
+    public PrivilegeDefinition[] getPrivilegeDefinitions(SessionInfo sessionInfo) throws RepositoryException {
+        return internalGetPrivilegeDefinitions(sessionInfo, uriResolver.getRepositoryUri());
+    }
+
+    @Override
+    public PrivilegeDefinition[] getSupportedPrivileges(SessionInfo sessionInfo, NodeId nodeId) throws RepositoryException {
+        String uri = (nodeId == null) ? uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName()) : getItemUri(nodeId, sessionInfo);
+        return internalGetPrivilegeDefinitions(sessionInfo, uri);
+    }
+
+    @Override
+    public Name[] getPrivilegeNames(SessionInfo sessionInfo, NodeId nodeId) throws RepositoryException {
+        String uri = (nodeId == null) ? uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName()) : getItemUri(nodeId, sessionInfo);
+        DavPropertyNameSet nameSet = new DavPropertyNameSet();
+        nameSet.add(SecurityConstants.CURRENT_USER_PRIVILEGE_SET);
+
+        DavMethodBase method = null;
+        try {
+            method = new PropFindMethod(uri, nameSet, DEPTH_0);
+            getClient(sessionInfo).executeMethod(method);
+
+            MultiStatusResponse[] responses = method.getResponseBodyAsMultiStatus().getResponses();
+            if (responses.length < 1) {
+                throw new PathNotFoundException("Unable to retrieve privileges definitions.");
+            }
+
+            DavPropertyName displayName = SecurityConstants.CURRENT_USER_PRIVILEGE_SET;
+            DavProperty<?> p = responses[0].getProperties(DavServletResponse.SC_OK).get(displayName);
+            if (p == null) {
+                return new Name[0];
+            } else {
+                Collection<Privilege> privs = new CurrentUserPrivilegeSetProperty(p).getValue();
+                Set<Name> privNames = new HashSet<Name>(privs.size());
+                for (Privilege priv : privs) {
+                    privNames.add(nameFactory.create(priv.getNamespace().getURI(), priv.getName()));
+                }
+                return privNames.toArray(new Name[privNames.size()]);
+            }
+        } catch (IOException e) {
+            throw new RepositoryException(e);
+        } catch (DavException e) {
+            throw ExceptionConverter.generate(e);
+        } finally {
+            if (method != null) {
+                method.releaseConnection();
+            }
+        }
+    }
+    
+    private PrivilegeDefinition[] internalGetPrivilegeDefinitions(SessionInfo sessionInfo, String uri) throws RepositoryException {
+        DavPropertyNameSet nameSet = new DavPropertyNameSet();
+        nameSet.add(SecurityConstants.SUPPORTED_PRIVILEGE_SET);
+        DavMethodBase method = null;
+        try {
+            method = new PropFindMethod(uri, nameSet, DEPTH_0);
+            getClient(sessionInfo).executeMethod(method);
+
+            MultiStatusResponse[] responses = method.getResponseBodyAsMultiStatus().getResponses();
+            if (responses.length < 1) {
+                throw new PathNotFoundException("Unable to retrieve privileges definitions.");
+            }
+
+            DavPropertyName displayName = SecurityConstants.SUPPORTED_PRIVILEGE_SET;
+            DavProperty<?> p = responses[0].getProperties(DavServletResponse.SC_OK).get(displayName);
+            if (p == null) {
+                return new PrivilegeDefinition[0];
+            } else {
+                // build PrivilegeDefinition(s) from the supported-privileges dav property
+                Map<Name, SupportedPrivilege> spMap = new HashMap<Name, SupportedPrivilege>();
+                fillSupportedPrivilegeMap(new SupportedPrivilegeSetProperty(p).getValue(), spMap, getNameFactory());
+
+                List<PrivilegeDefinition> pDefs = new ArrayList<PrivilegeDefinition>();
+                for (Name privilegeName : spMap.keySet()) {
+                    SupportedPrivilege sp = spMap.get(privilegeName);
+                    Set<Name> aggrnames = null;
+                    SupportedPrivilege[] aggregates = sp.getSupportedPrivileges();
+                    if (aggregates != null && aggregates.length > 0) {
+                        aggrnames = new HashSet<Name>();
+                        for (SupportedPrivilege aggregate : aggregates) {
+                            Name aggregateName = nameFactory.create(aggregate.getPrivilege().getNamespace().getURI(), 
+                                                                    aggregate.getPrivilege().getName());
+                            aggrnames.add(aggregateName);
+                        }
+                    }
+                    PrivilegeDefinition def = new PrivilegeDefinitionImpl(privilegeName, sp.isAbstract(), aggrnames);
+                    pDefs.add(def);
+                }
+                return pDefs.toArray(new PrivilegeDefinition[pDefs.size()]);
+            }
+        } catch (IOException e) {
+            throw new RepositoryException(e);
+        } catch (DavException e) {
+            throw ExceptionConverter.generate(e);
+        } finally {
+            if (method != null) {
+                method.releaseConnection();
+            }
+        }
+    }
+
+    private static void fillSupportedPrivilegeMap(List<SupportedPrivilege> sps, Map<Name, SupportedPrivilege> spMap, NameFactory nameFactory) throws NamespaceException, IllegalNameException {
+        for (SupportedPrivilege sp : sps) {
+            Privilege p = sp.getPrivilege();
+            Name privName = nameFactory.create(p.getNamespace().getURI(), p.getName());
+            spMap.put(privName, sp);
+            List<SupportedPrivilege> agg = Arrays.asList(sp.getSupportedPrivileges());
+            if (!agg.isEmpty()) {
+                fillSupportedPrivilegeMap(agg, spMap, nameFactory);
+            }
+        }
+    }
+
     /**
      * @see RepositoryService#getNodeDefinition(SessionInfo, NodeId)
      */
@@ -1014,9 +1156,9 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
             }
 
             NamePathResolver resolver = getNamePathResolver(sessionInfo);
-            NodeId parentId = getParentId(propSet, sessionInfo);
+            NodeId parentId = getParentId(uri, propSet, sessionInfo);
 
-            NodeInfoImpl nInfo = buildNodeInfo(nodeResponse, parentId, propSet, sessionInfo, resolver);
+            NodeInfoImpl nInfo = buildNodeInfo(uri, nodeResponse, parentId, propSet, sessionInfo, resolver);
 
             for (MultiStatusResponse resp : childResponses) {
                 DavPropertySet childProps = resp.getProperties(DavServletResponse.SC_OK);
@@ -1066,11 +1208,11 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
         }
     }
 
-    private NodeInfoImpl buildNodeInfo(MultiStatusResponse nodeResponse,
+    private NodeInfoImpl buildNodeInfo(String baseUri, MultiStatusResponse nodeResponse,
                                        NodeId parentId, DavPropertySet propSet,
                                        SessionInfo sessionInfo,
                                        NamePathResolver resolver) throws NameException, RepositoryException {
-        NodeId id = uriResolver.buildNodeId(parentId, nodeResponse, sessionInfo.getWorkspaceName(), getNamePathResolver(sessionInfo));
+        NodeId id = uriResolver.buildNodeId(parentId, baseUri, nodeResponse, sessionInfo.getWorkspaceName(), getNamePathResolver(sessionInfo));
         NodeInfoImpl nInfo = new NodeInfoImpl(id, propSet, resolver);
         DavProperty p = propSet.get(JcrRemotingConstants.JCR_REFERENCES_LN, ItemResourceConstants.NAMESPACE);
         if (p != null) {
@@ -1183,12 +1325,9 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
     public Iterator<PropertyId> getReferences(SessionInfo sessionInfo, NodeId nodeId, Name propertyName, boolean weakReferences) throws ItemNotFoundException, RepositoryException {
         // set of properties to be retrieved
         DavPropertyNameSet nameSet = new DavPropertyNameSet();
-        if (weakReferences) {
-            nameSet.add(JcrRemotingConstants.JCR_WEAK_REFERENCES_LN, ItemResourceConstants.NAMESPACE);
-        } else {
-            nameSet.add(JcrRemotingConstants.JCR_REFERENCES_LN, ItemResourceConstants.NAMESPACE);
-        }
-
+        String refType = weakReferences ? JcrRemotingConstants.JCR_WEAK_REFERENCES_LN : JcrRemotingConstants.JCR_REFERENCES_LN;
+        nameSet.add(refType, ItemResourceConstants.NAMESPACE);
+ 
         DavMethodBase method = null;
         try {
             String uri = getItemUri(nodeId, sessionInfo);
@@ -1205,18 +1344,13 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
             for (MultiStatusResponse resp : responses) {
                 if (isSameResource(uri, resp)) {
                     DavPropertySet props = resp.getProperties(DavServletResponse.SC_OK);
-                    DavProperty<?> p;
-                    if (weakReferences) {
-                        p = props.get(JcrRemotingConstants.JCR_WEAK_REFERENCES_LN, ItemResourceConstants.NAMESPACE);
-                    } else {
-                        p = props.get(JcrRemotingConstants.JCR_REFERENCES_LN, ItemResourceConstants.NAMESPACE);
-                    }
+                    DavProperty<?> p = props.get(refType, ItemResourceConstants.NAMESPACE);
 
                     if (p != null) {
                         refIds = new ArrayList<PropertyId>();
                         HrefProperty hp = new HrefProperty(p);
                         for (String propHref : hp.getHrefs()) {
-                            PropertyId propId = uriResolver.getPropertyId(propHref, sessionInfo);
+                            PropertyId propId = uriResolver.getPropertyId(resolve(uri, propHref), sessionInfo);
                             if (propertyName == null || propertyName.equals(propId.getName())) {
                                 refIds.add(propId);
                             }
@@ -1336,13 +1470,13 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
                 return qvs;
             }
         } catch (SAXException e) {
-            log.warn("Internal error: ", e.getMessage());
+            log.warn("Internal error: {}", e.getMessage());
             throw new RepositoryException(e);
         } catch (IOException e) {
-            log.warn("Internal error: ", e.getMessage());
+            log.warn("Internal error: {}", e.getMessage());
             throw new RepositoryException(e);
         } catch (ParserConfigurationException e) {
-            log.warn("Internal error: ", e.getMessage());
+            log.warn("Internal error: {}", e.getMessage());
             throw new RepositoryException(e);
         }
     }
@@ -1428,6 +1562,11 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
         }
     }
 
+    @Override
+    public Tree createTree(SessionInfo sessionInfo, Batch batch, Name nodeName, Name primaryTypeName, String uniqueId) throws RepositoryException {
+        return new DocumentTree(nodeName, primaryTypeName, uniqueId, getNamePathResolver(sessionInfo));
+    }
+
     /**
      * @see RepositoryService#importXml(SessionInfo, NodeId, InputStream, int)
      */
@@ -1447,6 +1586,9 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
     public void move(SessionInfo sessionInfo, NodeId srcNodeId, NodeId destParentNodeId, Name destName) throws RepositoryException {
         String uri = getItemUri(srcNodeId, sessionInfo);
         String destUri = getItemUri(destParentNodeId, destName, sessionInfo);
+        if (isDavClass3(sessionInfo)) {
+            destUri = obtainAbsolutePathFromUri(destUri);
+        }
         MoveMethod method = new MoveMethod(uri, destUri, false);
         execute(method, sessionInfo);
         // need to clear the cache as the move may have affected nodes with uuid.
@@ -1459,6 +1601,9 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
     public void copy(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNodeId, NodeId destParentNodeId, Name destName) throws RepositoryException {
         String uri = uriResolver.getItemUri(srcNodeId, srcWorkspaceName, sessionInfo);
         String destUri = getItemUri(destParentNodeId, destName, sessionInfo);
+        if (isDavClass3(sessionInfo)) {
+            destUri = obtainAbsolutePathFromUri(destUri);
+        }
         CopyMethod method = new CopyMethod(uri, destUri, false, false);
         execute(method, sessionInfo);
     }
@@ -1494,8 +1639,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
         try {
             String uri = getItemUri(nodeId, sessionInfo);
             method = new PropFindMethod(uri, nameSet, DEPTH_0);
-            // TODO: not correct. pass tokens in order avoid new session to be created TOBEFIXED
-            initMethod(method, sessionInfo, true);
+            initMethod(method, sessionInfo, false);
 
             getClient(sessionInfo).executeMethod(method);
             method.checkSuccess();
@@ -1509,7 +1653,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
             if (ps.contains(DavPropertyName.LOCKDISCOVERY)) {
                 DavProperty<?> p = ps.get(DavPropertyName.LOCKDISCOVERY);
                 LockDiscovery ld = LockDiscovery.createFromXml(p.toXml(DomUtil.createDocument()));
-                NodeId parentId = getParentId(ps, sessionInfo);
+                NodeId parentId = getParentId(uri, ps, sessionInfo);
                 return retrieveLockInfo(ld, sessionInfo, nodeId, parentId);
             }  else {
                 // no lock present
@@ -1571,7 +1715,9 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
         String uri = getItemUri(nodeId, sessionInfo);
         // since sessionInfo does not allow to retrieve token by NodeId,
         // pass all available lock tokens to the LOCK method (TODO: correct?)
-        LockMethod method = new LockMethod(uri, INFINITE_TIMEOUT, ((SessionInfoImpl) sessionInfo).getAllLockTokens());
+        Set<String> allLockTokens = ((SessionInfoImpl) sessionInfo).getAllLockTokens();
+        String[] locktokens = allLockTokens.toArray(new String[allLockTokens.size()]);
+        LockMethod method = new LockMethod(uri, INFINITE_TIMEOUT, locktokens);
         execute(method, sessionInfo);
     }
 
@@ -1592,6 +1738,10 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
         String lockToken = lInfo.getActiveLock().getToken();
         boolean isSessionScoped = lInfo.isSessionScoped();
 
+        if (!((SessionInfoImpl) sessionInfo).getAllLockTokens().contains(lockToken)) {
+            throw new LockException("Lock " + lockToken + " not owned by this session");
+        }
+
         UnLockMethod method = new UnLockMethod(uri, lockToken);
         execute(method, sessionInfo);
 
@@ -1600,6 +1750,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
 
     private LockInfo retrieveLockInfo(LockDiscovery lockDiscovery, SessionInfo sessionInfo,
                                       NodeId nodeId, NodeId parentId) throws RepositoryException {
+        checkSessionInfo(sessionInfo);
         List<ActiveLock> activeLocks = lockDiscovery.getValue();
         ActiveLock activeLock = null;
         for (ActiveLock l : activeLocks) {
@@ -1616,16 +1767,21 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
             log.debug("No lock present on node " + saveGetIdString(nodeId, sessionInfo));
             return null;
         }
-        if (activeLock.isDeep() && parentId != null) {
-            // try if lock is inherited
+
+        NodeId holder = null;
+        String lockroot = activeLock.getLockroot();
+        if (activeLock.getLockroot() != null) {
+            holder = uriResolver.getNodeId(lockroot, sessionInfo);
+        }
+
+        if (activeLock.isDeep() && holder == null && parentId != null) {
+            // deep lock, parent known, but holder is not
             LockInfo pLockInfo = getLockInfo(sessionInfo, parentId);
             if (pLockInfo != null) {
                 return pLockInfo;
             }
         }
-        // no deep lock or parentID == null or lock is not present on parent
-        // -> nodeID is lockHolding Id.
-        return new LockInfoImpl(activeLock, nodeId);
+        return new LockInfoImpl(activeLock, holder == null ? nodeId : holder, ((SessionInfoImpl)sessionInfo).getAllLockTokens());
     }
 
     /**
@@ -1636,7 +1792,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
         CheckinMethod method = new CheckinMethod(uri);
         execute(method, sessionInfo);
         Header rh = method.getResponseHeader(DeltaVConstants.HEADER_LOCATION);
-        return uriResolver.getNodeId(rh.getValue(), sessionInfo);
+        return uriResolver.getNodeId(resolve(uri, rh.getValue()), sessionInfo);
     }
 
     /**
@@ -1757,8 +1913,9 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
     private void update(String uri, Path relPath, String[] updateSource, int updateType, boolean removeExisting, SessionInfo sessionInfo) throws RepositoryException {
         try {
             UpdateInfo uInfo;
+            String tmpUpdateSource[] = obtainAbsolutePathsFromUris(updateSource);
             if (removeExisting || relPath != null) {
-                Element uElem = UpdateInfo.createUpdateElement(updateSource, updateType, DomUtil.createDocument());
+                Element uElem = UpdateInfo.createUpdateElement(tmpUpdateSource, updateType, DomUtil.createDocument());
                 if (removeExisting) {
                     DomUtil.addChildElement(uElem, JcrRemotingConstants.XML_REMOVEEXISTING, ItemResourceConstants.NAMESPACE);
                 }
@@ -1768,7 +1925,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
 
                 uInfo = new UpdateInfo(uElem);
             } else {
-                uInfo = new UpdateInfo(updateSource, updateType, new DavPropertyNameSet());
+                uInfo = new UpdateInfo(tmpUpdateSource, updateType, new DavPropertyNameSet());
             }
 
             UpdateMethod method = new UpdateMethod(uri, uInfo);
@@ -1795,20 +1952,21 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
     public Iterator<NodeId> merge(SessionInfo sessionInfo, NodeId nodeId, String srcWorkspaceName, boolean bestEffort, boolean isShallow) throws NoSuchWorkspaceException, AccessDeniedException, MergeException, LockException, InvalidItemStateException, RepositoryException {
         try {
             Document doc = DomUtil.createDocument();
-            String wspHref = uriResolver.getWorkspaceUri(srcWorkspaceName);
-            Element mElem = MergeInfo.createMergeElement(new String[] {wspHref}, !bestEffort, false, doc);
+            String wspHref = obtainAbsolutePathFromUri(uriResolver.getWorkspaceUri(srcWorkspaceName));
+            Element mElem = MergeInfo.createMergeElement(new String[] { wspHref }, !bestEffort, false, doc);
             if (isShallow) {
                 mElem.appendChild(DomUtil.depthToXml(false, doc));
             }
             MergeInfo mInfo = new MergeInfo(mElem);
 
-            MergeMethod method = new MergeMethod(getItemUri(nodeId, sessionInfo), mInfo);
+            String uri = getItemUri(nodeId, sessionInfo);
+            MergeMethod method = new MergeMethod(uri, mInfo);
             execute(method, sessionInfo);
 
             MultiStatusResponse[] resps = method.getResponseBodyAsMultiStatus().getResponses();
             List<NodeId> failedIds = new ArrayList<NodeId>(resps.length);
             for (MultiStatusResponse resp : resps) {
-                String href = resp.getHref();
+                String href = resolve(uri, resp.getHref());
                 failedIds.add(uriResolver.getNodeId(href, sessionInfo));
             }
             return failedIds.iterator();
@@ -1945,8 +2103,9 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
         SearchMethod method = null;
         try {
             String uri = uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName());
-            SearchInfo sInfo = new SearchInfo(language,
-                    Namespace.EMPTY_NAMESPACE, statement, namespaces);
+            SearchInfo sInfo = new SearchInfo(
+                    language, ItemResourceConstants.NAMESPACE,
+                    statement, namespaces);
 
             if (limit != -1) {
                 sInfo.setNumberResults(limit);
@@ -2067,7 +2226,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
                 Element contentElem = DomUtil.getChildElement(entryElem, AtomFeedConstants.N_CONTENT);
                 if (contentElem != null
                         && "application/vnd.apache.jackrabbit.event+xml".equals(contentElem.getAttribute("type"))) {
-                    List<Event> el = buildEventList(contentElem, (SessionInfoImpl) sessionInfo);
+                    List<Event> el = buildEventList(contentElem, (SessionInfoImpl) sessionInfo, rootUri);
                     for (Event e : el) {
                         if (e.getDate() > after && (filter == null || filter.accept(e, false))) {
                             events.add(e);
@@ -2226,21 +2385,19 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
             } else {
                 Element discEl = disc.toXml(DomUtil.createDocument());
                 ElementIterator it = DomUtil.getChildren(discEl,
-                        ObservationConstants.XML_EVENTBUNDLE,
-                        ObservationConstants.NAMESPACE);
+                        ObservationConstants.N_EVENTBUNDLE);
                 List<EventBundle> bundles = new ArrayList<EventBundle>();
                 while (it.hasNext()) {
                     Element bundleElement = it.nextElement();
                     String value = DomUtil.getAttribute(bundleElement,
-                            ObservationConstants.XML_EVENT_LOCAL,
-                            ObservationConstants.NAMESPACE);
+                            ObservationConstants.XML_EVENT_LOCAL, null);
                     // check if it matches a batch id recently submitted
                     boolean isLocal = false;
                     if (value != null) {
                         isLocal = Boolean.parseBoolean(value);
                     }
                     bundles.add(new EventBundleImpl(
-                            buildEventList(bundleElement, sessionInfo),
+                            buildEventList(bundleElement, sessionInfo, uri),
                             isLocal));
                 }
                 events = bundles.toArray(new EventBundle[bundles.size()]);
@@ -2259,9 +2416,10 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
         }
     }
 
-    private List<Event> buildEventList(Element bundleElement, SessionInfoImpl sessionInfo) throws IllegalNameException, NamespaceException {
+    private List<Event> buildEventList(Element bundleElement, SessionInfoImpl sessionInfo, String baseUri)
+            throws IllegalNameException, NamespaceException, RepositoryException {
         List<Event> events = new ArrayList<Event>();
-        ElementIterator eventElementIterator = DomUtil.getChildren(bundleElement, ObservationConstants.XML_EVENT, ObservationConstants.NAMESPACE);
+        ElementIterator eventElementIterator = DomUtil.getChildren(bundleElement, ObservationConstants.N_EVENT);
 
         String userId = null;
 
@@ -2276,7 +2434,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
 
         while (eventElementIterator.hasNext()) {
             Element evElem = eventElementIterator.nextElement();
-            Element typeEl = DomUtil.getChildElement(evElem, ObservationConstants.XML_EVENTTYPE, ObservationConstants.NAMESPACE);
+            Element typeEl = DomUtil.getChildElement(evElem, ObservationConstants.N_EVENTTYPE);
             EventType[] et = DefaultEventType.createFromXml(typeEl);
             if (et.length == 0 || et.length > 1) {
                 // should not occur.
@@ -2292,17 +2450,18 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
             NodeId parentId = null;
 
             if (href != null) {
+                href = resolve(baseUri, href);
                 try {
                     eventPath = uriResolver.getQPath(href, sessionInfo);
                 } catch (RepositoryException e) {
                     // should not occur
-                    log.error("Internal error while building Event", e.getMessage());
+                    log.error("Internal error while building Event: ()", e.getMessage());
                     continue;
                 }
 
                 boolean isForNode = (type == Event.NODE_ADDED
                         || type == Event.NODE_REMOVED || type == Event.NODE_MOVED);
-                
+
                 try {
                     if (isForNode) {
                         eventId = uriResolver.getNodeIdAfterEvent(href,
@@ -2320,7 +2479,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
                                             eventPath.getAncestor(1)),
                                     eventPath.getName());
                         } catch (RepositoryException e1) {
-                            log.warn("Unable to build event itemId: ",
+                            log.warn("Unable to build event itemId: {}",
                                     e.getMessage());
                         }
                     }
@@ -2330,14 +2489,14 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
                 try {
                     parentId = uriResolver.getNodeId(parentHref, sessionInfo);
                 } catch (RepositoryException e) {
-                    log.warn("Unable to build event parentId: ", e.getMessage());
+                    log.warn("Unable to build event parentId: {}", e.getMessage());
                 }
                 
             }
 
             if (userId == null) {
                 // user id not retrieved from container
-                userId = DomUtil.getChildTextTrim(evElem, ObservationConstants.XML_EVENTUSERID, ObservationConstants.NAMESPACE);
+                userId = DomUtil.getChildTextTrim(evElem, ObservationConstants.N_EVENTUSERID);
             }
 
             events.add(new EventImpl(eventId, eventPath, parentId, type, userId, evElem,
@@ -2621,6 +2780,26 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
     }
 
     /**
+     * Compute the repository URI (while dealing with trailing / and port number
+     * defaulting)
+     */
+    public static URI computeRepositoryUri(String uri) throws URIException {
+        URI repositoryUri = new URI((uri.endsWith("/")) ? uri : uri + "/", true);
+        // workaround for JCR-3228: normalize default port numbers because of
+        // the weak URI matching code elsewhere (the remote server is unlikely
+        // to include the port number in URIs when it's the default for the
+        // protocol)
+        boolean useDefaultPort = ("http".equalsIgnoreCase(repositoryUri.getScheme()) && repositoryUri.getPort() == 80)
+                || (("https".equalsIgnoreCase(repositoryUri.getScheme()) && repositoryUri.getPort() == 443));
+        if (useDefaultPort) {
+            repositoryUri = new URI(repositoryUri.getScheme(), repositoryUri.getUserinfo(), repositoryUri.getHost(), -1,
+                    repositoryUri.getPath(), repositoryUri.getQuery(), repositoryUri.getFragment());
+        }
+
+        return repositoryUri;
+    }
+
+    /**
      *
      * @param sessionInfo
      * @param reportDoc
@@ -2749,15 +2928,70 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
         return new DefaultDavProperty<List<XmlSerializable>>(localName, val, ItemResourceConstants.NAMESPACE, false);
     }
 
-    /**
-     * The XML elements and attributes used in serialization
-     */
-    private static final Namespace SV_NAMESPACE = Namespace.getNamespace(Name.NS_SV_PREFIX, Name.NS_SV_URI);
-    private static final String NODE_ELEMENT = "node";
-    private static final String PROPERTY_ELEMENT = "property";
-    private static final String VALUE_ELEMENT = "value";
-    private static final String NAME_ATTRIBUTE = "name";
-    private static final String TYPE_ATTRIBUTE = "type";
+    private Set<String> getDavComplianceClasses(SessionInfo sessionInfo) throws RepositoryException {
+        if (this.remoteDavComplianceClasses == null) {
+            OptionsMethod method = new OptionsMethod(uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName()));
+            try {
+                getClient(sessionInfo).executeMethod(method);
+                method.checkSuccess();
+                Header davHeader = method.getResponseHeader("DAV");
+                if (davHeader!= null) {
+                    // TODO: think about coded-URLs containing a comma
+                    String[] classes = davHeader.getValue().split(",");
+                    this.remoteDavComplianceClasses = new HashSet<String>();
+                    for (String c : classes) {
+                        this.remoteDavComplianceClasses.add(c.trim());
+                    }
+                }
+            } catch (IOException e) {
+                throw new RepositoryException(e);
+            } catch (DavException e) {
+                throw ExceptionConverter.generate(e);
+            } finally {
+                method.releaseConnection();
+            }
+        }
+        return this.remoteDavComplianceClasses;
+    }
+
+    private boolean isDavClass3(SessionInfo sessionInfo) {
+        try {
+            return getDavComplianceClasses(sessionInfo).contains("3");
+        }
+        catch (RepositoryException ex) {
+            log.warn("failure to obtain OPTIONS response", ex);
+            return false;
+        }
+    }
+
+    private static String obtainAbsolutePathFromUri(String uri) {
+        try {
+            java.net.URI u = new java.net.URI(uri);
+            StringBuilder sb = new StringBuilder();
+            sb.append(u.getRawPath());
+            if (u.getRawQuery() != null) {
+                sb.append("?").append(u.getRawQuery());
+            }
+            return sb.toString();
+        }
+        catch (java.net.URISyntaxException ex) {
+            log.warn("parsing " + uri, ex);
+            return uri;
+        }
+    }
+
+    private static String[] obtainAbsolutePathsFromUris(String[] uris) {
+        if (uris == null) {
+            return null;
+        } else {
+            String result[] = new String[uris.length];
+
+            for (int i = 0; i < result.length; i++) {
+                result[i] = obtainAbsolutePathFromUri(uris[i]);
+            }
+            return result;
+        }
+    }
 
     //------------------------------------------------< Inner Class 'Batch' >---
     private class BatchImpl implements Batch {
@@ -2857,9 +3091,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
         }
 
         //----------------------------------------------------------< Batch >---
-        /**
-         * @see Batch#addNode(NodeId, Name, Name, String)
-         */
+        @Override
         public void addNode(NodeId parentId, Name nodeName, Name nodetypeName, String uuid) throws RepositoryException {
             checkConsumed();
             try {
@@ -2871,25 +3103,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
 
                 // build 'sys-view' for the node to create and append it as request body
                 Document body = DomUtil.createDocument();
-                Element nodeElement = DomUtil.addChildElement(body, NODE_ELEMENT, SV_NAMESPACE);
-                String nameAttr = resolver.getJCRName(nodeName);
-                DomUtil.setAttribute(nodeElement, NAME_ATTRIBUTE, SV_NAMESPACE, nameAttr);
-
-                // nodetype must never be null
-                Element propElement = DomUtil.addChildElement(nodeElement, PROPERTY_ELEMENT, SV_NAMESPACE);
-                String name = resolver.getJCRName(NameConstants.JCR_PRIMARYTYPE);
-                DomUtil.setAttribute(propElement, NAME_ATTRIBUTE, SV_NAMESPACE, name);
-                DomUtil.setAttribute(propElement, TYPE_ATTRIBUTE, SV_NAMESPACE, PropertyType.nameFromValue(PropertyType.NAME));
-                name = resolver.getJCRName(nodetypeName);
-                DomUtil.addChildElement(propElement, VALUE_ELEMENT, SV_NAMESPACE, name);
-                // optional uuid
-                if (uuid != null) {
-                    propElement = DomUtil.addChildElement(nodeElement, PROPERTY_ELEMENT, SV_NAMESPACE);
-                    name = resolver.getJCRName(NameConstants.JCR_UUID);
-                    DomUtil.setAttribute(propElement, NAME_ATTRIBUTE, SV_NAMESPACE, name);
-                    DomUtil.setAttribute(propElement, TYPE_ATTRIBUTE, SV_NAMESPACE, PropertyType.nameFromValue(PropertyType.STRING));
-                    DomUtil.addChildElement(propElement, VALUE_ELEMENT, SV_NAMESPACE, uuid);
-                }
+                BatchUtils.createNodeElement(body, nodeName, nodetypeName, uuid, resolver);
                 method.setRequestBody(body);
 
                 methods.add(method);
@@ -2900,9 +3114,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
             }
         }
 
-        /**
-         * @see Batch#addProperty(NodeId, Name, QValue)
-         */
+        @Override
         public void addProperty(NodeId parentId, Name propertyName, QValue value) throws RepositoryException {
             checkConsumed();
             String uri = getItemUri(parentId, propertyName, sessionInfo);
@@ -2913,9 +3125,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
             methods.add(method);
         }
 
-        /**
-         * @see Batch#addProperty(NodeId, Name, QValue[])
-         */
+        @Override
         public void addProperty(NodeId parentId, Name propertyName, QValue[] values) throws RepositoryException {
             checkConsumed();
             // TODO: avoid usage of the ValuesProperty. specially for binary props.
@@ -2936,9 +3146,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
             }
         }
 
-        /**
-         * @see Batch#setValue(PropertyId, QValue)
-         */
+        @Override
         public void setValue(PropertyId propertyId, QValue value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
             checkConsumed();
             if (value == null) {
@@ -2956,9 +3164,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
             }
         }
 
-        /**
-         * @see Batch#setValue(PropertyId, QValue[])
-         */
+        @Override
         public void setValue(PropertyId propertyId, QValue[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
             checkConsumed();
             if (values == null) {
@@ -3012,9 +3218,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
             return ent;
         }
 
-        /**
-         * @see Batch#remove(ItemId)
-         */
+        @Override
         public void remove(ItemId itemId) throws RepositoryException {
             checkConsumed();
             String uri = getItemUri(itemId, sessionInfo);
@@ -3026,9 +3230,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
             }
         }
 
-        /**
-         * @see Batch#reorderNodes(NodeId, NodeId, NodeId)
-         */
+        @Override
         public void reorderNodes(NodeId parentId, NodeId srcNodeId, NodeId beforeNodeId) throws RepositoryException {
             checkConsumed();
             try {
@@ -3052,9 +3254,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
             }
         }
 
-        /**
-         * @see Batch#setMixins(NodeId, Name[])
-         */
+        @Override
         public void setMixins(NodeId nodeId, Name[] mixinNodeTypeIds) throws NoSuchNodeTypeException, VersionException, ConstraintViolationException, LockException, AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
             checkConsumed();
             try {
@@ -3083,9 +3283,7 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
             }
         }
 
-        /**
-         * @see Batch#setPrimaryType(NodeId, Name)
-         */
+        @Override
         public void setPrimaryType(NodeId nodeId, Name primaryNodeTypeName) throws RepositoryException {
             checkConsumed();
             try {
@@ -3101,18 +3299,41 @@ public class RepositoryServiceImpl implements RepositoryService, DavConstants {
             }
         }
 
-        /**
-         * @see Batch#move(NodeId, NodeId, Name)
-         */
+        @Override
         public void move(NodeId srcNodeId, NodeId destParentNodeId, Name destName) throws RepositoryException {
             checkConsumed();
             String uri = getItemUri(srcNodeId, sessionInfo);
             String destUri = getItemUri(destParentNodeId, destName, sessionInfo);
+            if (isDavClass3(sessionInfo)) {
+                destUri = obtainAbsolutePathFromUri(destUri);
+            }
             MoveMethod method = new MoveMethod(uri, destUri, false);
 
             methods.add(method);
             clear = true;
         }
+
+        @Override
+        public void setTree(NodeId parentId, Tree tree) throws RepositoryException {
+            checkConsumed();
+
+            if (!(tree instanceof DocumentTree)) {
+                throw new RepositoryException("Invalid tree implementation " + tree.getClass().getName());
+            }
+            try {
+                // TODO: TOBEFIXED. WebDAV does not allow MKCOL for existing resource -> problem with SNS
+                // use fake name instead (see also #importXML)
+                Name fakeName = getNameFactory().create(Name.NS_DEFAULT_URI, UUID.randomUUID().toString());
+                String uri = getItemUri(parentId, fakeName, sessionInfo);
+                MkColMethod method = new MkColMethod(uri);
+
+                method.setRequestBody(((DocumentTree) tree).toDocument());
+
+                methods.add(method);
+            } catch (IOException e) {
+                throw new RepositoryException(e);
+            }
+        }
     }
 
     //----------------------------------------------< NamespaceResolverImpl >---
diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/SessionInfoImpl.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/SessionInfoImpl.java
index c637576..2777a9f 100644
--- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/SessionInfoImpl.java
+++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/SessionInfoImpl.java
@@ -18,6 +18,7 @@ package org.apache.jackrabbit.spi2dav;
 
 import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
 
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.Arrays;
@@ -31,8 +32,8 @@ public class SessionInfoImpl extends org.apache.jackrabbit.spi.commons.SessionIn
     private final CredentialsWrapper credentials;
     private final Set<String> sessionScopedTokens = new HashSet<String>();
     
-    // a globally unique URI identifiying this session
-    private final String sessionIdentifier = "urn:uuid" + UUID.randomUUID();
+    // a globally unique URI identifying this session
+    private final String sessionIdentifier = "urn:uuid:" + UUID.randomUUID();
     
     private String lastBatchId;
     private NamePathResolver resolver;
@@ -98,10 +99,10 @@ public class SessionInfoImpl extends org.apache.jackrabbit.spi.commons.SessionIn
      * communication with the DAV server and are never exposed through the
      * JCR API for they belong to session-scoped locks.
      */
-    String[] getAllLockTokens() {
+    Set<String> getAllLockTokens() {
         Set<String> s = new HashSet<String>(Arrays.asList(getLockTokens()));
         s.addAll(sessionScopedTokens);
-        return s.toArray(new String[s.size()]);
+        return Collections.unmodifiableSet(s);
     }
 
     void addLockToken(String token, boolean sessionScoped) {
diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/URIResolverImpl.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/URIResolverImpl.java
index c5124e9..7e9138a 100644
--- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/URIResolverImpl.java
+++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/URIResolverImpl.java
@@ -45,6 +45,7 @@ import org.w3c.dom.Document;
 import javax.jcr.ItemNotFoundException;
 import javax.jcr.RepositoryException;
 import java.io.IOException;
+import java.net.URISyntaxException;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -125,8 +126,9 @@ class URIResolverImpl implements URIResolver {
 
                         MultiStatus ms = rm.getResponseBodyAsMultiStatus();
                         if (ms.getResponses().length == 1) {
-                            uriBuffer.append(ms.getResponses()[0].getHref());
-                            cache.add(ms.getResponses()[0].getHref(), uuidId);
+                            String absoluteUri = resolve(wspUri, ms.getResponses()[0].getHref());
+                            uriBuffer.append(absoluteUri);
+                            cache.add(absoluteUri, uuidId);
                         } else {
                             throw new ItemNotFoundException("Cannot identify item with uniqueID " + uniqueID);
                         }
@@ -161,10 +163,24 @@ class URIResolverImpl implements URIResolver {
         }
     }
 
-    NodeId buildNodeId(NodeId parentId, MultiStatusResponse response,
+    /**
+     * Resolve the given href obtained from multistatus against base URI
+     */
+    private static String resolve(String wspUri, String href) throws RepositoryException {
+        try {
+            java.net.URI base = new java.net.URI(wspUri);
+            java.net.URI rel = new java.net.URI(href);
+            return base.resolve(rel).toString();
+        }
+        catch (URISyntaxException ex) {
+            throw new RepositoryException(ex);
+        }
+    }
+
+    protected NodeId buildNodeId(NodeId parentId, String baseUri, MultiStatusResponse response,
                        String workspaceName, NamePathResolver resolver) throws RepositoryException {
         IdURICache cache = getCache(workspaceName);
-        
+
         NodeId nodeId;
         DavPropertySet propSet = response.getProperties(DavServletResponse.SC_OK);
 
@@ -181,7 +197,7 @@ class URIResolverImpl implements URIResolver {
             }
         }
         // cache
-        cache.add(response.getHref(), nodeId);
+        cache.add(resolve(baseUri, response.getHref()), nodeId);
         return nodeId;
     }
 
@@ -233,6 +249,7 @@ class URIResolverImpl implements URIResolver {
     }
 
     private NodeId getNodeId(String uri, SessionInfo sessionInfo, boolean nodeIsGone) throws RepositoryException {
+
         IdURICache cache = getCache(sessionInfo.getWorkspaceName());
         if (cache.containsUri(uri)) {
             // id has been accessed before and is cached
@@ -268,7 +285,7 @@ class URIResolverImpl implements URIResolver {
             if (responses.length != 1) {
                 throw new ItemNotFoundException("Unable to retrieve the node with id " + uri);
             }
-            return buildNodeId(parentId, responses[0], sessionInfo.getWorkspaceName(), service.getNamePathResolver(sessionInfo));
+            return buildNodeId(parentId, uri, responses[0], sessionInfo.getWorkspaceName(), service.getNamePathResolver(sessionInfo));
 
         } catch (IOException e) {
             throw new RepositoryException(e);
diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/QValueFactoryImpl.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/QValueFactoryImpl.java
index d00f089..ac1edbf 100644
--- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/QValueFactoryImpl.java
+++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/QValueFactoryImpl.java
@@ -171,7 +171,7 @@ class QValueFactoryImpl extends org.apache.jackrabbit.spi.commons.value.QValueFa
          * <code>InputStream</code>. The contents of the stream is spooled
          * to a temporary file or to a byte buffer if its size is smaller than
          * {@link #MAX_BUFFER_SIZE}.
-         * <p/>
+         * <p>
          * The <code>temp</code> parameter governs whether dynamically allocated
          * resources will be freed explicitly on {@link #discard()}. Note that any
          * dynamically allocated resources (temp file/buffer) will be freed
diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/RepositoryServiceImpl.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/RepositoryServiceImpl.java
index 762dfa0..af42b3e 100644
--- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/RepositoryServiceImpl.java
+++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/RepositoryServiceImpl.java
@@ -16,18 +16,30 @@
  */
 package org.apache.jackrabbit.spi2davex;
 
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import javax.jcr.Credentials;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+
 import org.apache.commons.httpclient.HttpClient;
 import org.apache.commons.httpclient.HttpException;
 import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.URI;
+import org.apache.commons.httpclient.URIException;
 import org.apache.commons.httpclient.methods.RequestEntity;
 import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
 import org.apache.commons.httpclient.methods.multipart.Part;
-import org.apache.commons.httpclient.methods.multipart.PartBase;
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.jackrabbit.commons.json.JsonParser;
 import org.apache.jackrabbit.commons.json.JsonUtil;
 import org.apache.jackrabbit.commons.webdav.JcrRemotingConstants;
-import org.apache.jackrabbit.commons.webdav.JcrValueType;
 import org.apache.jackrabbit.commons.webdav.ValueUtil;
 import org.apache.jackrabbit.spi.Batch;
 import org.apache.jackrabbit.spi.ItemId;
@@ -40,6 +52,7 @@ import org.apache.jackrabbit.spi.PropertyInfo;
 import org.apache.jackrabbit.spi.QValue;
 import org.apache.jackrabbit.spi.RepositoryService;
 import org.apache.jackrabbit.spi.SessionInfo;
+import org.apache.jackrabbit.spi.Tree;
 import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl;
 import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
 import org.apache.jackrabbit.spi.commons.conversion.PathResolver;
@@ -49,6 +62,7 @@ import org.apache.jackrabbit.spi.commons.name.NameConstants;
 import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
 import org.apache.jackrabbit.spi.commons.name.PathBuilder;
 import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
+import org.apache.jackrabbit.spi.commons.tree.AbstractTree;
 import org.apache.jackrabbit.spi.commons.value.ValueFormat;
 import org.apache.jackrabbit.spi2dav.ExceptionConverter;
 import org.apache.jackrabbit.spi2dav.ItemResourceConstants;
@@ -66,18 +80,6 @@ import org.apache.jackrabbit.webdav.property.DavPropertySet;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.jcr.Credentials;
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
 /**
  * <code>RepositoryServiceImpl</code>...
  */
@@ -97,8 +99,6 @@ public class RepositoryServiceImpl extends org.apache.jackrabbit.spi2dav.Reposit
     private static final String ORDER_POSITION_LAST = "#last";
     private static final String ORDER_POSITION_BEFORE = "#before";
 
-    private static final String DEFAULT_CHARSET = "UTF-8";
-
     private static final DavPropertyName JCR_TYPE =
             DavPropertyName.create(ItemResourceConstants.JCR_TYPE_LN, ItemResourceConstants.NAMESPACE);
 
@@ -190,7 +190,13 @@ public class RepositoryServiceImpl extends org.apache.jackrabbit.spi2dav.Reposit
         super(jcrServerURI, IdFactoryImpl.getInstance(), NameFactoryImpl.getInstance(),
                 PathFactoryImpl.getInstance(), new QValueFactoryImpl(), itemInfoCacheSize, maximumHttpConnections);
 
-        this.jcrServerURI = jcrServerURI.endsWith("/") ? jcrServerURI : jcrServerURI + "/";
+        try {
+            URI repositoryUri = computeRepositoryUri(jcrServerURI);
+            this.jcrServerURI = repositoryUri.toString();
+        } catch (URIException e) {
+            throw new RepositoryException(e);
+        }
+
         this.defaultWorkspaceName = defaultWorkspaceName;
         if (batchReadConfig == null) {
             this.batchReadConfig = new BatchReadConfig() {
@@ -457,6 +463,11 @@ public class RepositoryServiceImpl extends org.apache.jackrabbit.spi2dav.Reposit
     }
 
     @Override
+    public Tree createTree(SessionInfo sessionInfo, Batch batch, Name nodeName, Name primaryTypeName, String uniqueId) throws RepositoryException {
+        return new JsonTree(sessionInfo, nodeName, primaryTypeName, uniqueId, getNamePathResolver(sessionInfo));
+    }
+
+    @Override
     public void copy(SessionInfo sessionInfo, String srcWorkspaceName, NodeId srcNodeId, NodeId destParentNodeId, Name destName) throws RepositoryException {
         if (srcWorkspaceName.equals(sessionInfo.getWorkspaceName())) {
             super.copy(sessionInfo, srcWorkspaceName, srcNodeId, destParentNodeId, destName);
@@ -586,17 +597,12 @@ public class RepositoryServiceImpl extends org.apache.jackrabbit.spi2dav.Reposit
                 }
             }
 
-            if (parts.isEmpty()) {
-                // only a diff part. no multipart required.
-                method.addParameter(PARAM_DIFF, buf.toString());
-            } else {
-                // other parts are present -> add the diff part
-                addPart(PARAM_DIFF, buf.toString());
-                // ... and create multipart-entity (and set it to method)
-                Part[] partArr = parts.toArray(new Part[parts.size()]);
-                RequestEntity entity = new MultipartRequestEntity(partArr, method.getParams());
-                method.setRequestEntity(entity);
-            }
+            // add the diff part - always do multipart in case the receiving servlet
+            // engine has a form-size restriction (JCR-3726)
+            Utils.addPart(PARAM_DIFF, buf.toString(), parts);
+            Part[] partArr = parts.toArray(new Part[parts.size()]);
+            RequestEntity entity = new MultipartRequestEntity(partArr, method.getParams());
+            method.setRequestEntity(entity);
 
             HttpClient client = getClient(sessionInfo);
             try {
@@ -618,11 +624,9 @@ public class RepositoryServiceImpl extends org.apache.jackrabbit.spi2dav.Reposit
             method = null;
             isConsumed = true;
             // discard binary parts (JCR-2582)
-            if (parts != null) {
-                for (Part part : parts) {
-                    if (part instanceof BinaryPart) {
-                        ((BinaryPart) part).dispose();
-                    }
+            for (Part part : parts) {
+                if (part instanceof BinaryPart) {
+                    ((BinaryPart) part).dispose();
                 }
             }
         }
@@ -652,9 +656,7 @@ public class RepositoryServiceImpl extends org.apache.jackrabbit.spi2dav.Reposit
         }
 
         //----------------------------------------------------------< Batch >---
-        /**
-         * @inheritDoc
-         */
+        @Override
         public void addNode(NodeId parentId, Name nodeName, Name nodetypeName,
                             String uuid) throws RepositoryException {
             assertMethod();
@@ -665,56 +667,46 @@ public class RepositoryServiceImpl extends org.apache.jackrabbit.spi2dav.Reposit
 
             StringWriter wr = new StringWriter();
             wr.write('{');
-            wr.write(getJsonKey(JcrConstants.JCR_PRIMARYTYPE));
+            wr.write(Utils.getJsonKey(JcrConstants.JCR_PRIMARYTYPE));
             wr.write(JsonUtil.getJsonString(getNamePathResolver(sessionInfo).getJCRName(nodetypeName)));
             if (uuid != null) {
                 wr.write(',');
-                wr.write(getJsonKey(JcrConstants.JCR_UUID));
+                wr.write(Utils.getJsonKey(JcrConstants.JCR_UUID));
                 wr.write(JsonUtil.getJsonString(uuid));
             }
             wr.write('}');
             appendDiff(SYMBOL_ADD_NODE, jcrPath, wr.toString());
         }
 
-        /**
-         * @inheritDoc
-         */
+        @Override
         public void addProperty(NodeId parentId, Name propertyName, QValue value) throws RepositoryException {
             assertMethod();
             Path p = getPathFactory().create(getPath(parentId, sessionInfo), propertyName, true);
             setProperty(p, value, false);
         }
 
-        /**
-         * @inheritDoc
-         */
+        @Override
         public void addProperty(NodeId parentId, Name propertyName, QValue[] values) throws RepositoryException {
             assertMethod();
             Path p = getPathFactory().create(getPath(parentId, sessionInfo), propertyName, true);
             setProperty(p, values, false);
         }
 
-        /**
-         * @inheritDoc
-         */
+        @Override
         public void setValue(PropertyId propertyId, QValue value) throws RepositoryException {
             assertMethod();
             Path p = getPath(propertyId, sessionInfo);
             setProperty(p, value, true);
         }
 
-        /**
-         * @inheritDoc
-         */
+        @Override
         public void setValue(PropertyId propertyId, QValue[] values) throws RepositoryException {
             assertMethod();
             Path p = getPath(propertyId, sessionInfo);
             setProperty(p, values, true);
         }
 
-        /**
-         * @inheritDoc
-         */
+        @Override
         public void remove(ItemId itemId) throws RepositoryException {
             assertMethod();
 
@@ -731,9 +723,7 @@ public class RepositoryServiceImpl extends org.apache.jackrabbit.spi2dav.Reposit
             }
         }
 
-        /**
-         * @inheritDoc
-         */
+        @Override
         public void reorderNodes(NodeId parentId, NodeId srcNodeId, NodeId beforeNodeId) throws RepositoryException {
             assertMethod();
 
@@ -757,9 +747,7 @@ public class RepositoryServiceImpl extends org.apache.jackrabbit.spi2dav.Reposit
             }
         }
 
-        /**
-         * @inheritDoc
-         */
+        @Override
         public void setMixins(NodeId nodeId, Name[] mixinNodeTypeNames) throws RepositoryException {
             assertMethod();
 
@@ -773,9 +761,7 @@ public class RepositoryServiceImpl extends org.apache.jackrabbit.spi2dav.Reposit
             setProperty(p, vs, true);
         }
 
-        /**
-         * @inheritDoc
-         */
+        @Override
         public void setPrimaryType(NodeId nodeId, Name primaryNodeTypeName) throws RepositoryException {
             assertMethod();
 
@@ -786,9 +772,7 @@ public class RepositoryServiceImpl extends org.apache.jackrabbit.spi2dav.Reposit
             setProperty(p, v, true);
         }
 
-        /**
-         * @inheritDoc
-         */
+        @Override
         public void move(NodeId srcNodeId, NodeId destParentNodeId, Name destName) throws RepositoryException {
             assertMethod();
 
@@ -801,6 +785,19 @@ public class RepositoryServiceImpl extends org.apache.jackrabbit.spi2dav.Reposit
             clear = true;
         }
 
+        @Override
+        public void setTree(NodeId parentId, Tree contentTree) throws RepositoryException {
+            assertMethod();
+            if (!(contentTree instanceof JsonTree)) {
+                throw new RepositoryException("Invalid Tree implementation : " + contentTree.getClass().getName());
+            }
+
+            Path normalizedPath = getPathFactory().create(getPath(parentId, sessionInfo), contentTree.getName(), true);
+            String jcrPath = getNamePathResolver(sessionInfo).getJCRPath(normalizedPath);
+            appendDiff(SYMBOL_ADD_NODE, jcrPath, ((JsonTree) contentTree).toJsonString(parts));
+        }
+
+        //----------------------------------------------------------------------
         /**
          *
          * @param symbol
@@ -829,10 +826,10 @@ public class RepositoryServiceImpl extends org.apache.jackrabbit.spi2dav.Reposit
                 clearPreviousSetProperty(jcrPropPath);
             }
 
-            String strValue = getJsonString(value);
+            String strValue = Utils.getJsonString(value);
             appendDiff(SYMBOL_SET_PROPERTY, jcrPropPath, strValue);
             if (strValue == null) {
-                addPart(jcrPropPath, value, resolver);
+                Utils.addPart(jcrPropPath, value, resolver, parts);
             }
         }
 
@@ -845,9 +842,9 @@ public class RepositoryServiceImpl extends org.apache.jackrabbit.spi2dav.Reposit
 
             StringBuilder strVal = new StringBuilder("[");
             for (int i = 0; i < values.length; i++) {
-                String str = getJsonString(values[i]);
+                String str = Utils.getJsonString(values[i]);
                 if (str == null) {
-                    addPart(jcrPropPath, values[i], resolver);
+                    Utils.addPart(jcrPropPath, values[i], resolver, parts);
                 } else {
                     String delim = (i == 0) ? "" : ",";
                     strVal.append(delim).append(str);
@@ -867,85 +864,12 @@ public class RepositoryServiceImpl extends org.apache.jackrabbit.spi2dav.Reposit
                 String entry = it.next();
                 if (entry.startsWith(key)) {
                     it.remove();
-                    removeParts(jcrPropPath);
+                    Utils.removeParts(jcrPropPath, parts);
                     return;
                 }
             }
         }
 
-        /**
-         *
-         * @param paramName
-         * @param value
-         */
-        private void addPart(String paramName, String value) {
-            parts.add(new StringPart(paramName, value, DEFAULT_CHARSET));
-        }
-
-        /**
-         *
-         * @param paramName
-         * @param value
-         * @param resolver
-         * @throws RepositoryException
-         */
-        private void addPart(String paramName, QValue value, NamePathResolver resolver) throws RepositoryException {
-            Part part;
-            switch (value.getType()) {
-                case PropertyType.BINARY:
-                    part = new BinaryPart(paramName, new BinaryPartSource(value), JcrValueType.contentTypeFromType(PropertyType.BINARY), DEFAULT_CHARSET);
-                    break;
-                case PropertyType.NAME:
-                    part = new StringPart(paramName, resolver.getJCRName(value.getName()), DEFAULT_CHARSET);
-                    break;
-                case PropertyType.PATH:
-                    part = new StringPart(paramName, resolver.getJCRPath(value.getPath()), DEFAULT_CHARSET);
-                    break;
-                default:
-                    part = new StringPart(paramName, value.getString(), DEFAULT_CHARSET);
-            }
-            String ctype = JcrValueType.contentTypeFromType(value.getType());
-            ((PartBase) part).setContentType(ctype);
-
-            parts.add(part);
-        }
-
-        private void removeParts(String paramName) {
-            for (Iterator<Part> it = parts.iterator(); it.hasNext();) {
-                Part part = it.next();
-                if (part.getName().equals(paramName)) {
-                    it.remove();
-                }
-            }
-        }
-
-        private String getJsonKey(String str) {
-            return JsonUtil.getJsonString(str) + ":";
-        }
-
-        private String getJsonString(QValue value) throws RepositoryException {
-            String str;
-            switch (value.getType()) {
-                case PropertyType.STRING:
-                    str = JsonUtil.getJsonString(value.getString());
-                    break;
-                case PropertyType.BOOLEAN:
-                case PropertyType.LONG:
-                    str = value.getString();
-                    break;
-                case PropertyType.DOUBLE:
-                    str = value.getString();
-                    if (str.indexOf('.') == -1) {
-                        str += ".0";
-                    }
-                    break;
-                default:
-                    // JSON cannot specifically handle this property type...
-                    str = null;
-            }
-            return str;
-        }
-
         private Path calcRemovePath(Path removedNodePath) throws RepositoryException {
             removed.put(removedNodePath, removedNodePath);
             Name name = removedNodePath.getName();
@@ -972,4 +896,108 @@ public class RepositoryServiceImpl extends org.apache.jackrabbit.spi2dav.Reposit
             return removedNodePath;
         }
     }
+    
+    //--------------------------------------------------------------------------
+    class JsonTree extends AbstractTree {
+
+        private final StringBuilder properties = new StringBuilder();
+        private final List<Part> parts = new ArrayList<Part>();
+        private final SessionInfo sessionInfo;
+        
+        JsonTree(SessionInfo sessionInfo, Name nodeName, Name ntName, String uniqueId, NamePathResolver resolver) {
+            super(nodeName, ntName, uniqueId, resolver);
+            this.sessionInfo = sessionInfo;
+        }
+
+        //-------------------------------------------------------< AbstractTree >---
+        @Override
+        protected Tree createChild(Name name, Name primaryTypeName, String uniqueId) {
+            return new JsonTree(sessionInfo, name, primaryTypeName, uniqueId, getResolver());
+        }
+
+        //---------------------------------------------------------------< Tree >---
+        @Override
+        public void addProperty(NodeId parentId, Name propertyName, int propertyType, QValue value) throws RepositoryException {
+            properties.append(',');
+            properties.append(Utils.getJsonKey(getResolver().getJCRName(propertyName)));
+            
+            String valueStr = Utils.getJsonString(value);
+            if (valueStr == null) {            	  
+            	String jcrPropPath = createPath(parentId, propertyName);
+            	Utils.addPart(jcrPropPath, value, getResolver(), parts);
+            } else {
+            	properties.append(valueStr);
+            }
+            
+        }
+
+        @Override
+        public void addProperty(NodeId parentId, Name propertyName, int propertyType, QValue[] values) throws RepositoryException {
+            String name = getResolver().getJCRName(propertyName);
+            properties.append(',');
+            properties.append(Utils.getJsonKey(name));
+            int index = 0;
+            properties.append('[');
+            for (QValue value : values) {
+                String valueStr = Utils.getJsonString(value);
+                if (valueStr == null) {                	
+                	String jcrPropPath = createPath(parentId, propertyName);
+                    Utils.addPart(jcrPropPath, value, getResolver(), parts);
+                } else {
+                    String delim = (index++ == 0) ? "" : ",";
+                    properties.append(delim).append('"').append(valueStr).append('"');
+                }
+            }
+            properties.append(']');
+        }
+        
+        private String createPath(NodeId parentId, Name propertyName) throws RepositoryException {
+        	Path propPath = getPathFactory().create(getPath(parentId, sessionInfo), propertyName, true);
+        	return getResolver().getJCRPath(propPath);
+        }
+        
+        //--------------------------------------------------------------------------
+        String toJsonString(List<Part> batchParts) throws RepositoryException {
+        	batchParts.addAll(this.parts);
+        	for (Tree child : this.getChildren()) {
+        		batchParts.addAll(((JsonTree) child).getParts());
+        	}        
+
+            StringBuilder json = new StringBuilder();
+            createJsonNodeFragment(json, this, true);
+            return json.toString();
+        }    
+        
+        //--------------------------------------------------------------------------
+        private String createJsonNodeFragment(StringBuilder json, JsonTree tree, boolean start) throws RepositoryException {
+            if (!start) {
+                json.append(',');
+                json.append(Utils.getJsonKey(getResolver().getJCRName(tree.getName())));
+            }
+            json.append('{');
+            json.append(Utils.getJsonKey(JcrConstants.JCR_PRIMARYTYPE));
+            json.append(JsonUtil.getJsonString(getResolver().getJCRName(tree.getPrimaryTypeName())));
+            String uuid = tree.getUniqueId();
+            if (uuid != null) {
+                json.append(',');
+                json.append(Utils.getJsonKey(JcrConstants.JCR_UUID));
+                json.append(JsonUtil.getJsonString(uuid));
+            }
+            // write all the properties.
+            json.append(tree.getProperties());
+            for (Tree child : tree.getChildren()) {
+                createJsonNodeFragment(json, (JsonTree) child, false);
+            }
+            json.append('}');
+            return json.toString();
+        }
+        
+        private StringBuilder getProperties() {
+        	return properties;
+        }
+        
+        private List<Part> getParts() {
+        	return parts;
+        }
+    }
 }
diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Spi2davexRepositoryServiceFactory.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Spi2davexRepositoryServiceFactory.java
index 20067f8..4686084 100644
--- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Spi2davexRepositoryServiceFactory.java
+++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Spi2davexRepositoryServiceFactory.java
@@ -66,6 +66,11 @@ public class Spi2davexRepositoryServiceFactory implements RepositoryServiceFacto
      */
     public static final String PARAM_MAX_CONNECTIONS = "org.apache.jackrabbit.spi2davex.MaxConnections";
 
+
+    public static final String PARAM_WORKSPACE_NAME_DEFAULT =  "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault";
+
+    public static final String DEFAULT_WORKSPACE_NAME_DEFAULT = "default";
+
     public RepositoryService createRepositoryService(Map<?, ?> parameters) throws RepositoryException {
         // retrieve the repository uri
         String uri;
@@ -84,6 +89,8 @@ public class Spi2davexRepositoryServiceFactory implements RepositoryServiceFacto
         int itemInfoCacheSize = ItemInfoCacheImpl.DEFAULT_CACHE_SIZE;
         int maximumHttpConnections = 0;
 
+        String workspaceNameDefault = DEFAULT_WORKSPACE_NAME_DEFAULT;
+
         if (parameters != null) {
             // batchRead config
             Object param = parameters.get(PARAM_BATCHREAD_CONFIG);
@@ -110,12 +117,17 @@ public class Spi2davexRepositoryServiceFactory implements RepositoryServiceFacto
                     // using default
                 }
             }
+
+            param = parameters.get(PARAM_WORKSPACE_NAME_DEFAULT);
+            if (param != null) {
+                workspaceNameDefault = param.toString();
+            }
         }
 
         if (maximumHttpConnections > 0) {
-            return new RepositoryServiceImpl(uri, null, brc, itemInfoCacheSize, maximumHttpConnections);
+            return new RepositoryServiceImpl(uri, workspaceNameDefault, brc, itemInfoCacheSize, maximumHttpConnections);
         } else {
-            return new RepositoryServiceImpl(uri, null, brc, itemInfoCacheSize);
+            return new RepositoryServiceImpl(uri, workspaceNameDefault, brc, itemInfoCacheSize);
         }
     }
 
diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Utils.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Utils.java
new file mode 100644
index 0000000..234db3b
--- /dev/null
+++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/Utils.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.spi2davex;
+
+import java.util.Iterator;
+import java.util.List;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.httpclient.methods.multipart.Part;
+import org.apache.commons.httpclient.methods.multipart.PartBase;
+import org.apache.jackrabbit.commons.webdav.JcrValueType;
+import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.commons.json.JsonUtil;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+
+final class Utils {
+
+    private static final String DEFAULT_CHARSET = "UTF-8";
+
+    private Utils() {};
+
+    static String getJsonKey(String str) {
+        return JsonUtil.getJsonString(str) + ":";
+    }
+
+    static String getJsonString(QValue value) throws RepositoryException {
+        String str;
+        switch (value.getType()) {
+            case PropertyType.STRING:
+                str = JsonUtil.getJsonString(value.getString());
+                break;
+            case PropertyType.BOOLEAN:
+            case PropertyType.LONG:
+                str = value.getString();
+                break;
+            case PropertyType.DOUBLE:
+                double d = value.getDouble();
+                if (Double.isNaN(d) || Double.isInfinite(d)) {
+                    // JSON cannot specifically handle this property type...
+                    str = null;
+                } else {
+                    str = value.getString();
+                    if (str.indexOf('.') == -1) {
+                        str += ".0";
+                    }
+                }
+                break;
+            default:
+                // JSON cannot specifically handle this property type...
+                str = null;
+        }
+        return str;
+    }
+
+    /**
+     *
+     * @param paramName
+     * @param value
+     */
+    static void addPart(String paramName, String value, List<Part> parts) {
+        parts.add(new StringPart(paramName, value, DEFAULT_CHARSET));
+    }
+
+    /**
+     *
+     * @param paramName
+     * @param value
+     * @param resolver
+     * @throws RepositoryException
+     */
+    static void addPart(String paramName, QValue value, NamePathResolver resolver, List<Part> parts) throws RepositoryException {
+        Part part;
+        switch (value.getType()) {
+            case PropertyType.BINARY:
+                part = new BinaryPart(paramName, new BinaryPartSource(value), JcrValueType.contentTypeFromType(PropertyType.BINARY), DEFAULT_CHARSET);
+                break;
+            case PropertyType.NAME:
+                part = new StringPart(paramName, resolver.getJCRName(value.getName()), DEFAULT_CHARSET);
+                break;
+            case PropertyType.PATH:
+                part = new StringPart(paramName, resolver.getJCRPath(value.getPath()), DEFAULT_CHARSET);
+                break;
+            default:
+                part = new StringPart(paramName, value.getString(), DEFAULT_CHARSET);
+        }
+        String ctype = JcrValueType.contentTypeFromType(value.getType());
+        ((PartBase) part).setContentType(ctype);
+
+        parts.add(part);
+    }
+
+    static void removeParts(String paramName, List<Part> parts) {
+        for (Iterator<Part> it = parts.iterator(); it.hasNext();) {
+            Part part = it.next();
+            if (part.getName().equals(paramName)) {
+                it.remove();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/ValueLoader.java b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/ValueLoader.java
index cc552b5..07aec16 100644
--- a/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/ValueLoader.java
+++ b/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2davex/ValueLoader.java
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.spi2davex;
 
+import org.apache.commons.httpclient.Header;
 import org.apache.commons.httpclient.HttpClient;
 import org.apache.commons.httpclient.methods.GetMethod;
 import org.apache.commons.httpclient.methods.HeadMethod;
@@ -74,7 +75,10 @@ class ValueLoader {
             if (statusCode == DavServletResponse.SC_OK) {
                 Map<String, String> headers = new HashMap<String, String>();
                 for (String name : headerNames) {
-                    headers.put(name, method.getResponseHeader(name).getValue());
+                    Header hdr = method.getResponseHeader(name);
+                    if (hdr != null) {
+                        headers.put(name, hdr.getValue());
+                    }
                 }
                 return headers;
             } else {
diff --git a/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/RepositoryStubImpl.java b/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/RepositoryStubImpl.java
index 86fc1b4..24164f0 100644
--- a/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/RepositoryStubImpl.java
+++ b/jackrabbit-spi2dav/src/test/java/org/apache/jackrabbit/spi2dav/RepositoryStubImpl.java
@@ -72,7 +72,7 @@ public class RepositoryStubImpl extends RepositoryStub {
                     }
                 });
             } catch (Exception e) {
-                throw new RepositoryStubException(e.toString());
+                throw new RepositoryStubException(e);
             }
         }
         return repository;
diff --git a/jackrabbit-spi2dav/src/test/resources/repositoryStubImpl.properties b/jackrabbit-spi2dav/src/test/resources/repositoryStubImpl.properties
index 97cfb92..02781e0 100644
--- a/jackrabbit-spi2dav/src/test/resources/repositoryStubImpl.properties
+++ b/jackrabbit-spi2dav/src/test/resources/repositoryStubImpl.properties
@@ -158,9 +158,9 @@ javax.jcr.tck.SessionTest.testMoveItemExistsException.nodetype2=nt:folder
 javax.jcr.tck.SessionTest.testMoveItemExistsException.nodetype3=nt:hierarchyNode
 
 # Test class: SessionTest
-# Test method: testSaveContstraintViolationException
+# Test method: testSaveConstraintViolationException
 # nodetype that has a property that is mandatory but not autocreated
-javax.jcr.tck.SessionTest.testSaveContstraintViolationException.nodetype2=nt:file
+javax.jcr.tck.SessionTest.testSaveConstraintViolationException.nodetype2=nt:file
 
 # Test class: SessionUUIDTest
 # node type that has a property of type PropertyType.REFERENCE
@@ -191,9 +191,9 @@ javax.jcr.tck.NodeTest.testRemoveMandatoryNode.nodetype3=nt:base
 javax.jcr.tck.NodeTest.testRemoveMandatoryNode.nodename3=jcr:content
 
 # Test class: NodeTest
-# Test method: testSaveContstraintViolationException
+# Test method: testSaveConstraintViolationException
 # nodetype that has a property that is mandatory but not autocreated
-javax.jcr.tck.NodeTest.testSaveContstraintViolationException.nodetype2=nt:file
+javax.jcr.tck.NodeTest.testSaveConstraintViolationException.nodetype2=nt:file
 
 # Test class: NodeUUIDTest
 # node type that has a property of type PropertyType.REFERENCE
diff --git a/jackrabbit-spi2jcr/pom.xml b/jackrabbit-spi2jcr/pom.xml
index 60f4239..0ca3449 100644
--- a/jackrabbit-spi2jcr/pom.xml
+++ b/jackrabbit-spi2jcr/pom.xml
@@ -26,7 +26,7 @@
   <parent>
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>jackrabbit-parent</artifactId>
-    <version>2.3.6</version>
+    <version>2.10.1</version>
     <relativePath>../jackrabbit-parent/pom.xml</relativePath>
   </parent>
   <artifactId>jackrabbit-spi2jcr</artifactId>
@@ -57,11 +57,28 @@
                 org.apache.jackrabbit.jcr2spi.name.NamespaceRegistryTest#testReRegisteredNamespaceVisibility
                 org.apache.jackrabbit.jcr2spi.name.NamespaceRegistryTest#testRegisteredNamespaceVisibility
                 org.apache.jackrabbit.test.api.ShareableNodeTest
+                org.apache.jackrabbit.test.api.observation.NodeReorderTest#testNodeReorderMove
                 org.apache.jackrabbit.test.api.version.simple
                 org.apache.jackrabbit.test.api.LifecycleTest
+org.apache.jackrabbit.test.api.query.qom.EquiJoinConditionTest#testRightOuterJoin1<!--JCR-3493, JCR-3498-->
+org.apache.jackrabbit.test.api.query.qom.EquiJoinConditionTest#testLeftOuterJoin2<!--JCR-3493, JCR-3498-->
+
+                <!-- security related tests -->
+                <!-- The following 5 tests don't make too much sense on a jcr-client that doesn't necessarily should
+                     know about the concrete implementation and thus might just collection all information and
+                     delegate any optimization to the server-side -->
+                org.apache.jackrabbit.test.api.security.AccessControlListTest#testAddAggregatedPrivilegesSeparately <!-- JCR-3832 -->
+                org.apache.jackrabbit.test.api.security.AccessControlListTest#testAddAccessControlEntryInvalidPrincipal
+                org.apache.jackrabbit.test.api.security.AccessControlListTest#testAddAccessControlEntryInvalidPrivilege
+                org.apache.jackrabbit.test.api.security.AccessControlListTest#testAddAccessControlEntryTwice
+                org.apache.jackrabbit.test.api.security.AccessControlListTest#testAddAccessControlEntryAgain
+                <!-- Tests to verify -->
+                org.apache.jackrabbit.test.api.security.RSessionAccessControlPolicyTest#testGetApplicablePolicies
+                org.apache.jackrabbit.test.api.security.RSessionAccessControlPolicyTest#testGetPolicy
               </value>
             </property>
           </systemProperties>
+          <runOrder>reversealphabetical</runOrder>
         </configuration>
       </plugin>
       <plugin>
@@ -85,26 +102,31 @@
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
+      <artifactId>jackrabbit-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-spi</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <classifier />
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-spi</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <classifier>tests</classifier>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-spi-commons</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-commons</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.slf4j</groupId>
@@ -118,7 +140,7 @@
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-tests</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
     <dependency>
@@ -129,20 +151,20 @@
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-core</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr2spi</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <classifier>tests</classifier>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr2spi</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <classifier />
       <scope>test</scope>
     </dependency>
diff --git a/jackrabbit-spi2jcr/src/main/java/org/apache/jackrabbit/spi2jcr/QueryInfoImpl.java b/jackrabbit-spi2jcr/src/main/java/org/apache/jackrabbit/spi2jcr/QueryInfoImpl.java
index 0e6ad23..c3f2e08 100644
--- a/jackrabbit-spi2jcr/src/main/java/org/apache/jackrabbit/spi2jcr/QueryInfoImpl.java
+++ b/jackrabbit-spi2jcr/src/main/java/org/apache/jackrabbit/spi2jcr/QueryInfoImpl.java
@@ -18,7 +18,6 @@ package org.apache.jackrabbit.spi2jcr;
 
 import org.apache.jackrabbit.spi.QueryInfo;
 import org.apache.jackrabbit.spi.QValueFactory;
-import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.commons.iterator.RangeIteratorAdapter;
 import org.apache.jackrabbit.commons.iterator.RangeIteratorDecorator;
 import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
@@ -30,8 +29,6 @@ import javax.jcr.query.Row;
 import javax.jcr.RepositoryException;
 import javax.jcr.RangeIterator;
 import java.util.NoSuchElementException;
-import java.util.List;
-import java.util.Arrays;
 
 /**
  * <code>QueryInfoImpl</code> implements a <code>QueryInfo</code> based on a
@@ -72,7 +69,7 @@ class QueryInfoImpl implements QueryInfo {
     /**
      * The names of the selectors in the query result.
      */
-    private final Name[] selectorNames;
+    private final String[] selectorNames;
 
     /**
      * Creates a new query info based on a given <code>result</code>.
@@ -94,7 +91,7 @@ class QueryInfoImpl implements QueryInfo {
         this.resolver = resolver;
         this.qValueFactory = qValueFactory;
         this.columnNames = result.getColumnNames();
-        this.selectorNames = getSelectorNames(result, resolver);
+        this.selectorNames = result.getSelectorNames();
     }
 
     /**
@@ -125,28 +122,14 @@ class QueryInfoImpl implements QueryInfo {
      * {@inheritDoc}
      */
     public String[] getColumnNames() {
-        String[] names = new String[columnNames.length];
-        System.arraycopy(columnNames, 0, names, 0, columnNames.length);
-        return names;
+        return columnNames;
     }
 
     /**
      * {@inheritDoc}
      */
-    public Name[] getSelectorNames() {
-        Name[] names = new Name[selectorNames.length];
-        System.arraycopy(selectorNames, 0, names, 0, selectorNames.length);
-        return names;
-    }
-
-    private static Name[] getSelectorNames(QueryResult result,
-                                           NamePathResolver resolver)
-            throws RepositoryException {
-        List<String> sn = Arrays.asList(result.getSelectorNames());
-        Name[] selectorNames = new Name[sn.size()];
-        for (int i = 0; i < sn.size(); i++) {
-            selectorNames[i] = resolver.getQName(sn.get(i));
-        }
+    public String[] getSelectorNames() {
         return selectorNames;
     }
+
 }
diff --git a/jackrabbit-spi2jcr/src/main/java/org/apache/jackrabbit/spi2jcr/QueryResultRowImpl.java b/jackrabbit-spi2jcr/src/main/java/org/apache/jackrabbit/spi2jcr/QueryResultRowImpl.java
index 4604fb4..96fb62b 100644
--- a/jackrabbit-spi2jcr/src/main/java/org/apache/jackrabbit/spi2jcr/QueryResultRowImpl.java
+++ b/jackrabbit-spi2jcr/src/main/java/org/apache/jackrabbit/spi2jcr/QueryResultRowImpl.java
@@ -26,7 +26,6 @@ import org.apache.jackrabbit.spi.QueryResultRow;
 import org.apache.jackrabbit.spi.NodeId;
 import org.apache.jackrabbit.spi.QValue;
 import org.apache.jackrabbit.spi.QValueFactory;
-import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.commons.value.ValueFormat;
 import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
 
@@ -44,12 +43,12 @@ class QueryResultRowImpl implements QueryResultRow {
     /**
      * The node ids of the underlying row.
      */
-    private final Map<Name, NodeId> nodeIds = new HashMap<Name, NodeId>();
+    private final Map<String, NodeId> nodeIds = new HashMap<String, NodeId>();
 
     /**
      * The score values for this row.
      */
-    private final Map<Name, Double> scores = new HashMap<Name, Double>();
+    private final Map<String, Double> scores = new HashMap<String, Double>();
 
     /**
      * The QValues for this row.
@@ -70,7 +69,7 @@ class QueryResultRowImpl implements QueryResultRow {
      */
     public QueryResultRowImpl(Row row,
                               String[] columnNames,
-                              Name[] selectorNames,
+                              String[] selectorNames,
                               IdFactoryImpl idFactory,
                               NamePathResolver resolver,
                               QValueFactory qValueFactory) throws RepositoryException {
@@ -83,21 +82,20 @@ class QueryResultRowImpl implements QueryResultRow {
                 values[i] = ValueFormat.getQValue(v, resolver, qValueFactory);
             }
         }
-        List<Name> selNames = new ArrayList<Name>();
+        List<String> selNames = new ArrayList<String>();
         selNames.addAll(Arrays.asList(selectorNames));
         if (selNames.isEmpty()) {
             selNames.add(null); // default selector
         }
-        for (Name sn : selNames) {
+        for (String sn : selNames) {
             Node n;
             double score;
             if (sn == null) {
                 n = row.getNode();
                 score = row.getScore();
             } else {
-                String selName = resolver.getJCRName(sn);
-                n = row.getNode(selName);
-                score = row.getScore(selName);
+                n = row.getNode(sn);
+                score = row.getScore(sn);
             }
             NodeId id = null;
             if (n != null) {
@@ -108,7 +106,7 @@ class QueryResultRowImpl implements QueryResultRow {
         }
     }
 
-    public NodeId getNodeId(Name selectorName) {
+    public NodeId getNodeId(String selectorName) {
         if (nodeIds.containsKey(selectorName)) {
             return nodeIds.get(selectorName);
         } else {
@@ -120,7 +118,7 @@ class QueryResultRowImpl implements QueryResultRow {
         }
     }
 
-    public double getScore(Name selectorName) {
+    public double getScore(String selectorName) {
         Double score;
         if (scores.containsKey(selectorName)) {
             score = scores.get(selectorName);
diff --git a/jackrabbit-spi2jcr/src/main/java/org/apache/jackrabbit/spi2jcr/RepositoryServiceImpl.java b/jackrabbit-spi2jcr/src/main/java/org/apache/jackrabbit/spi2jcr/RepositoryServiceImpl.java
index 3cb7087..25e7008 100644
--- a/jackrabbit-spi2jcr/src/main/java/org/apache/jackrabbit/spi2jcr/RepositoryServiceImpl.java
+++ b/jackrabbit-spi2jcr/src/main/java/org/apache/jackrabbit/spi2jcr/RepositoryServiceImpl.java
@@ -16,7 +16,9 @@
  */
 package org.apache.jackrabbit.spi2jcr;
 
+import org.apache.jackrabbit.api.JackrabbitWorkspace;
 import org.apache.jackrabbit.spi.ItemInfoCache;
+import org.apache.jackrabbit.spi.PrivilegeDefinition;
 import org.apache.jackrabbit.spi.RepositoryService;
 import org.apache.jackrabbit.spi.IdFactory;
 import org.apache.jackrabbit.spi.QValueFactory;
@@ -43,6 +45,7 @@ import org.apache.jackrabbit.spi.QNodeTypeDefinition;
 import org.apache.jackrabbit.spi.Event;
 import org.apache.jackrabbit.spi.ItemInfo;
 import org.apache.jackrabbit.spi.ChildInfo;
+import org.apache.jackrabbit.spi.Tree;
 import org.apache.jackrabbit.spi.commons.EventFilterImpl;
 import org.apache.jackrabbit.spi.commons.EventBundleImpl;
 import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl;
@@ -60,6 +63,7 @@ import org.apache.jackrabbit.spi.commons.conversion.NameException;
 import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
 import org.apache.jackrabbit.spi.commons.conversion.MalformedPathException;
 import org.apache.jackrabbit.spi.commons.conversion.DefaultNamePathResolver;
+import org.apache.jackrabbit.spi.commons.privilege.PrivilegeDefinitionImpl;
 import org.apache.jackrabbit.spi.commons.value.QValueFactoryImpl;
 import org.apache.jackrabbit.spi.commons.value.ValueFormat;
 import org.apache.jackrabbit.spi.commons.value.ValueFactoryQImpl;
@@ -92,6 +96,7 @@ import javax.jcr.ItemVisitor;
 import javax.jcr.ValueFactory;
 import javax.jcr.GuestCredentials;
 import javax.jcr.PropertyIterator;
+import javax.jcr.security.Privilege;
 import javax.jcr.util.TraversingItemVisitor;
 import javax.jcr.observation.ObservationManager;
 import javax.jcr.observation.EventListener;
@@ -335,6 +340,75 @@ public class RepositoryServiceImpl implements RepositoryService {
         }
     }
 
+    @Override
+    public PrivilegeDefinition[] getPrivilegeDefinitions(SessionInfo sessionInfo) throws RepositoryException {
+        SessionInfoImpl sInfo = getSessionInfoImpl(sessionInfo);
+        Session session = sInfo.getSession();
+        Workspace wsp = session.getWorkspace();
+        Collection<Privilege> privs;
+        if (wsp instanceof JackrabbitWorkspace) {
+            privs = Arrays.asList(((JackrabbitWorkspace) wsp).getPrivilegeManager().getRegisteredPrivileges());
+        } else {
+            Privilege jcrAll = session.getAccessControlManager().privilegeFromName(Privilege.JCR_ALL);
+            privs = new HashSet<Privilege>();
+            privs.add(jcrAll);
+            for (Privilege p : jcrAll.getAggregatePrivileges()) {
+                privs.add(p);
+            }
+        }
+
+        PrivilegeDefinition[] pDefs = new PrivilegeDefinition[privs.size()];
+        NamePathResolver npResolver = sInfo.getNamePathResolver();
+        int i = 0;
+        for (Privilege p : privs) {
+            Set<Name> aggrnames = null;
+            if (p.isAggregate()) {
+                aggrnames = new HashSet<Name>();
+                for (Privilege dap : p.getDeclaredAggregatePrivileges()) {
+                    aggrnames.add(npResolver.getQName(dap.getName()));
+                }
+            }
+            PrivilegeDefinition def = new PrivilegeDefinitionImpl(npResolver.getQName(p.getName()), p.isAbstract(), aggrnames);
+            pDefs[i++] = def;
+        }
+        return pDefs;
+    }
+
+    @Override
+    public Name[] getPrivilegeNames(SessionInfo sessionInfo, NodeId nodeId) throws RepositoryException {
+        SessionInfoImpl sInfo = getSessionInfoImpl(sessionInfo);
+        String path = (nodeId == null) ? null : pathForId(nodeId, sInfo);
+        NamePathResolver npResolver = sInfo.getNamePathResolver();
+        
+        Privilege[] privs = sInfo.getSession().getAccessControlManager().getPrivileges(path);
+        List<Name> names = new ArrayList<Name>(privs.length);
+        for (Privilege priv : privs) {
+            names.add(npResolver.getQName(priv.getName()));
+        }
+        return names.toArray(new Name[names.size()]);
+    }
+    
+    public PrivilegeDefinition[] getSupportedPrivileges(SessionInfo sessionInfo, NodeId nodeId) throws RepositoryException {
+        SessionInfoImpl sInfo = getSessionInfoImpl(sessionInfo);
+        String path = (nodeId == null) ? null : pathForId(nodeId, sInfo);
+
+        Privilege[] privs = sInfo.getSession().getAccessControlManager().getSupportedPrivileges(path);
+        PrivilegeDefinition[] pDefs = new PrivilegeDefinition[privs.length];
+        NamePathResolver npResolver = sInfo.getNamePathResolver();
+        for (int i = 0; i < privs.length; i++) {
+            Set<Name> aggrnames = null;
+            if (privs[i].isAggregate()) {
+                aggrnames = new HashSet<Name>();
+                for (Privilege dap : privs[i].getDeclaredAggregatePrivileges()) {
+                    aggrnames.add(npResolver.getQName(dap.getName()));
+                }
+            }
+            PrivilegeDefinition def = new PrivilegeDefinitionImpl(npResolver.getQName(privs[i].getName()), privs[i].isAbstract(), aggrnames);
+            pDefs[i] = def;
+        }
+        return pDefs;
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -520,6 +594,11 @@ public class RepositoryServiceImpl implements RepositoryService {
         }
     }
 
+    @Override
+    public Tree createTree(SessionInfo sessionInfo, Batch batch, Name nodeName, Name primaryTypeName, String uniqueId) throws RepositoryException {
+        return new XmlTree(nodeName, primaryTypeName, uniqueId, getSessionInfoImpl(sessionInfo).getNamePathResolver());
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -1394,6 +1473,8 @@ public class RepositoryServiceImpl implements RepositoryService {
             this.sInfo = sInfo;
         }
 
+        //----------------------------------------------------------< Batch >---
+        @Override
         public void addNode(final NodeId parentId,
                             final Name nodeName,
                             final Name nodetypeName,
@@ -1425,6 +1506,7 @@ public class RepositoryServiceImpl implements RepositoryService {
             });
         }
 
+        @Override
         public void addProperty(final NodeId parentId,
                                 final Name propertyName,
                                 final QValue value)
@@ -1441,6 +1523,7 @@ public class RepositoryServiceImpl implements RepositoryService {
             });
         }
 
+        @Override
         public void addProperty(final NodeId parentId,
                                 final Name propertyName,
                                 final QValue[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, PathNotFoundException, ItemExistsException, AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
@@ -1459,6 +1542,7 @@ public class RepositoryServiceImpl implements RepositoryService {
             });
         }
 
+        @Override
         public void setValue(final PropertyId propertyId, final QValue value)
                 throws RepositoryException {
             executeGuarded(new Callable() {
@@ -1472,6 +1556,7 @@ public class RepositoryServiceImpl implements RepositoryService {
             });
         }
 
+        @Override
         public void setValue(final PropertyId propertyId, final QValue[] values)
                 throws RepositoryException {
             executeGuarded(new Callable() {
@@ -1488,6 +1573,7 @@ public class RepositoryServiceImpl implements RepositoryService {
             });
         }
 
+        @Override
         public void remove(final ItemId itemId) throws RepositoryException {
             executeGuarded(new Callable() {
                 public Object run() throws RepositoryException {
@@ -1535,6 +1621,7 @@ public class RepositoryServiceImpl implements RepositoryService {
             return nodeId;
         }
 
+        @Override
         public void reorderNodes(final NodeId parentId,
                                  final NodeId srcNodeId,
                                  final NodeId beforeNodeId)
@@ -1564,6 +1651,7 @@ public class RepositoryServiceImpl implements RepositoryService {
             });
         }
 
+        @Override
         public void setMixins(final NodeId nodeId,
                               final Name[] mixinNodeTypeIds)
                 throws RepositoryException {
@@ -1592,6 +1680,7 @@ public class RepositoryServiceImpl implements RepositoryService {
             });
         }
 
+        @Override
         public void setPrimaryType(final NodeId nodeId, final Name primaryNodeTypeName) throws RepositoryException {
             executeGuarded(new Callable() {
                 public Object run() throws RepositoryException {
@@ -1602,6 +1691,7 @@ public class RepositoryServiceImpl implements RepositoryService {
             });
         }
 
+        @Override
         public void move(final NodeId srcNodeId,
                          final NodeId destParentNodeId,
                          final Name destName) throws RepositoryException {
@@ -1619,6 +1709,36 @@ public class RepositoryServiceImpl implements RepositoryService {
             });
         }
 
+        @Override
+        public void setTree(final NodeId parentId, Tree tree) throws RepositoryException {
+            if (!(tree instanceof XmlTree)) {
+                throw new RepositoryException("Unknown Tree implementation: " + tree.getClass().getName());
+            }
+
+            final XmlTree xmlTree = (XmlTree) tree;
+            executeGuarded(new Callable() {
+                public Object run() throws RepositoryException {
+                    Session s = sInfo.getSession();
+                    Node parent = getParent(parentId, sInfo);
+                    String xml = xmlTree.toXML();;
+                    InputStream in = new ByteArrayInputStream(xml.getBytes());
+                    try {
+                        s.importXML(parent.getPath(), in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
+                    } catch (IOException e) {
+                        throw new RepositoryException(e.getMessage(), e);
+                    } finally {
+                        try {
+                            in.close();
+                        } catch (IOException e) {
+                            throw new RepositoryException(e.getMessage(), e);
+                        }
+                    }
+                    return null;
+                }
+            });
+        }
+
+        //----------------------------------------------------------------------
         private void executeGuarded(Callable call) throws RepositoryException {
             if (failed) {
                 return;
diff --git a/jackrabbit-spi2jcr/src/main/java/org/apache/jackrabbit/spi2jcr/XmlTree.java b/jackrabbit-spi2jcr/src/main/java/org/apache/jackrabbit/spi2jcr/XmlTree.java
new file mode 100644
index 0000000..6f8a629
--- /dev/null
+++ b/jackrabbit-spi2jcr/src/main/java/org/apache/jackrabbit/spi2jcr/XmlTree.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.spi2jcr;
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.spi.QValue;
+import org.apache.jackrabbit.spi.Tree;
+import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+import org.apache.jackrabbit.spi.commons.tree.AbstractTree;
+
+class XmlTree extends AbstractTree {
+
+    private final StringBuilder properties = new StringBuilder();
+
+    protected XmlTree(Name nodeName, Name ntName, String uniqueId, NamePathResolver resolver) {
+        super(nodeName, ntName, uniqueId, resolver);
+    }
+
+    //-------------------------------------------------------< AbstractTree >---
+    @Override
+    protected Tree createChild(Name name, Name primaryTypeName, String uniqueId) {
+        return new XmlTree(name, primaryTypeName, uniqueId, getResolver());
+    }
+
+    //---------------------------------------------------------------< Tree >---
+    @Override
+    public void addProperty(NodeId parentId, Name propertyName, int propertyType, QValue value) throws RepositoryException {
+        addProperty(parentId, propertyName, propertyType, new QValue[] {value});
+
+    }
+
+    @Override
+    public void addProperty(NodeId parentId, Name propertyName, int propertyType, QValue[] values) throws RepositoryException {
+        properties.append("<sv:property sv:name=\"").append(getResolver().getJCRName(propertyName)).append("\"");
+        properties.append(" sv:type=\"").append(PropertyType.nameFromValue(propertyType) + "\">");
+        for (QValue value : values) {
+            properties.append("<sv:value>").append(value.getString()).append("</sv:value>");
+        }
+        properties.append("</sv:property>");
+    }
+
+    //--------------------------------------------------------------------------
+    String toXML() throws RepositoryException {
+        StringBuilder xml = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+        createXMLNodeFragment(xml, this, getResolver(), true);
+        return xml.toString();
+    }
+
+    private static void createXMLNodeFragment(StringBuilder xml, XmlTree tree, NamePathResolver resolver, boolean includeNsInfo) throws RepositoryException {
+        xml.append("<sv:node ");
+        if (includeNsInfo) {
+           xml.append("xmlns:jcr=\"http://www.jcp.org/jcr/1.0\" xmlns:sv=\"http://www.jcp.org/jcr/sv/1.0\" ");
+        }
+        xml.append("sv:name=\"").append(resolver.getJCRName(tree.getName())).append("\">");
+        // jcr:primaryType
+        xml.append("<sv:property sv:name=\"jcr:primaryType\" sv:type=\"Name\">");
+        xml.append("<sv:value>").append(resolver.getJCRName(tree.getPrimaryTypeName())).append("</sv:value>");
+        xml.append("</sv:property>");
+        // jcr:uuid
+        String uniqueId = tree.getUniqueId();
+        if (uniqueId != null) {
+            xml.append("<sv:property sv:name=\"jcr:uuid\" sv:type=\"String\">");
+            xml.append("<sv:value>").append(uniqueId).append("</sv:value>");
+            xml.append("</sv:property>");
+        }
+
+        // create the xml fragment for all the child properties.
+        xml.append(tree.properties);
+
+        // create xml for all child nodes
+        for (Tree child : tree.getChildren()) {
+            createXMLNodeFragment(xml, (XmlTree) child, resolver, false);
+        }
+
+        xml.append("</sv:node>");
+    }
+}
diff --git a/jackrabbit-spi2jcr/src/test/java/org/apache/jackrabbit/spi2jcr/RepositoryStubImpl.java b/jackrabbit-spi2jcr/src/test/java/org/apache/jackrabbit/spi2jcr/RepositoryStubImpl.java
index 94dcb6f..bbb249b 100644
--- a/jackrabbit-spi2jcr/src/test/java/org/apache/jackrabbit/spi2jcr/RepositoryStubImpl.java
+++ b/jackrabbit-spi2jcr/src/test/java/org/apache/jackrabbit/spi2jcr/RepositoryStubImpl.java
@@ -17,14 +17,18 @@
 package org.apache.jackrabbit.spi2jcr;
 
 import org.apache.jackrabbit.core.JackrabbitRepositoryStub;
+import org.apache.jackrabbit.core.security.principal.EveryonePrincipal;
 import org.apache.jackrabbit.jcr2spi.AbstractRepositoryConfig;
 import org.apache.jackrabbit.jcr2spi.RepositoryImpl;
 import org.apache.jackrabbit.spi.RepositoryService;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.test.NotExecutableException;
 import org.apache.jackrabbit.test.RepositoryStubException;
 
 import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import java.security.Principal;
 import java.util.Properties;
 
 /**
@@ -63,14 +67,17 @@ public class RepositoryStubImpl extends JackrabbitRepositoryStub {
                     }
                 });
             } catch (RepositoryException e) {
-                RepositoryStubException ex = new RepositoryStubException(e.getMessage());
-                ex.initCause(e);
-                throw ex;
+                throw new RepositoryStubException(e);
             }
         }
         return repo;
     }
 
+    @Override
+    public Principal getKnownPrincipal(Session session) throws RepositoryException {
+        return EveryonePrincipal.getInstance();
+    }
+
     /**
      *
      * @return
diff --git a/jackrabbit-spi2jcr/src/test/resources/repository.xml b/jackrabbit-spi2jcr/src/test/resources/repository.xml
index 09c2f5a..37d5c61 100644
--- a/jackrabbit-spi2jcr/src/test/resources/repository.xml
+++ b/jackrabbit-spi2jcr/src/test/resources/repository.xml
@@ -15,8 +15,8 @@
    See the License for the specific language governing permissions and
    limitations under the License.
 -->
-<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
-                            "http://jackrabbit.apache.org/dtd/repository-1.6.dtd">
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 2.0//EN"
+                            "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">
 <!-- Example Repository Configuration File
      Used by
      - org.apache.jackrabbit.core.config.RepositoryConfigTest.java
@@ -105,6 +105,13 @@
             <param name="extractorPoolSize" value="2"/>
             <param name="supportHighlighting" value="true"/>
         </SearchIndex>
+
+        <!--
+        XML Import configuration of the workspace
+        -->
+        <Import>
+           <ProtectedItemImporter class="org.apache.jackrabbit.core.xml.AccessControlImporter"/>
+        </Import>
     </Workspace>
 
     <!--
diff --git a/jackrabbit-standalone/pom.xml b/jackrabbit-standalone/pom.xml
index b0d8782..7a977c3 100644
--- a/jackrabbit-standalone/pom.xml
+++ b/jackrabbit-standalone/pom.xml
@@ -26,7 +26,7 @@
   <parent>
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>jackrabbit-parent</artifactId>
-    <version>2.3.6</version>
+    <version>2.10.1</version>
     <relativePath>../jackrabbit-parent/pom.xml</relativePath>
   </parent>
   <artifactId>jackrabbit-standalone</artifactId>
@@ -71,19 +71,19 @@
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-webapp</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <type>war</type>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-webapp</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
       <type>jar</type>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr2dav</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.mortbay.jetty</groupId>
diff --git a/jackrabbit-standalone/src/main/appended-resources/META-INF/LICENSE b/jackrabbit-standalone/src/main/appended-resources/META-INF/LICENSE
index 5bed970..c83d80d 100644
--- a/jackrabbit-standalone/src/main/appended-resources/META-INF/LICENSE
+++ b/jackrabbit-standalone/src/main/appended-resources/META-INF/LICENSE
@@ -721,62 +721,6 @@ Array utility code in Lucene Java (lucene-core)
     ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
     OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-Commons Codec library (commons-codec-1.2.jar)
-
-    ====================================================================
-
-    The Apache Software License, Version 1.1
-
-    Copyright (c) 2001-2004 The Apache Software Foundation.  All rights
-    reserved.
-
-    Redistribution and use in source and binary forms, with or without
-    modification, are permitted provided that the following conditions
-    are met:
-
-    1. Redistributions of source code must retain the above copyright
-       notice, this list of conditions and the following disclaimer.
-
-    2. Redistributions in binary form must reproduce the above copyright
-       notice, this list of conditions and the following disclaimer in
-       the documentation and/or other materials provided with the
-       distribution.
-
-    3. The end-user documentation included with the redistribution,
-       if any, must include the following acknowledgement:
-          "This product includes software developed by the
-           Apache Software Foundation (http://www.apache.org/)."
-       Alternately, this acknowledgement may appear in the software itself,
-       if and wherever such third-party acknowledgements normally appear.
-
-    4. The names "Apache", "The Jakarta Project", "Commons", and "Apache Software
-       Foundation" must not be used to endorse or promote products derived
-       from this software without prior written permission. For written
-       permission, please contact apache at apache.org.
-
-    5. Products derived from this software may not be called "Apache"
-       nor may "Apache" appear in their name without prior
-       written permission of the Apache Software Foundation.
-
-    THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
-    WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-    DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
-    ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
-    USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-    OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-    OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-    SUCH DAMAGE.
-    ====================================================================
-
-    This software consists of voluntary contributions made by many
-    individuals on behalf of the Apache Software Foundation.  For more
-    information on the Apache Software Foundation, please see
-    <http://www.apache.org/>.
-
 JCR 2.0 API (jcr-2.0.jar)
 
     [Day Specification License]
@@ -928,6 +872,7 @@ JCR 2.0 API (jcr-2.0.jar)
     and use a Compliant Implementation.
 
 Eclipse JDT Core (core-3.1.1.jar)
+AspectJ runtime library (aspectjrt)
 
     Eclipse Public License - v 1.0
 
@@ -1465,3 +1410,475 @@ JSP2.1 Jasper implementation from Glassfish (jsp-2.1-6.1.14.jar)
     jurisdiction of the Federal Courts of the Northern District of California
     and the state courts of the State of California, with venue lying in
     Santa Clara County, California.
+
+juniversalchardet library (juniversalchardet)
+
+                              MOZILLA PUBLIC LICENSE
+                                    Version 1.1
+
+                                  ---------------
+
+    1. Definitions.
+
+         1.0.1. "Commercial Use" means distribution or otherwise making the
+         Covered Code available to a third party.
+
+         1.1. "Contributor" means each entity that creates or contributes to
+         the creation of Modifications.
+
+         1.2. "Contributor Version" means the combination of the Original
+         Code, prior Modifications used by a Contributor, and the Modifications
+         made by that particular Contributor.
+
+         1.3. "Covered Code" means the Original Code or Modifications or the
+         combination of the Original Code and Modifications, in each case
+         including portions thereof.
+
+         1.4. "Electronic Distribution Mechanism" means a mechanism generally
+         accepted in the software development community for the electronic
+         transfer of data.
+
+         1.5. "Executable" means Covered Code in any form other than Source
+         Code.
+
+         1.6. "Initial Developer" means the individual or entity identified
+         as the Initial Developer in the Source Code notice required by Exhibit
+         A.
+
+         1.7. "Larger Work" means a work which combines Covered Code or
+         portions thereof with code not governed by the terms of this License.
+
+         1.8. "License" means this document.
+
+         1.8.1. "Licensable" means having the right to grant, to the maximum
+         extent possible, whether at the time of the initial grant or
+         subsequently acquired, any and all of the rights conveyed herein.
+
+         1.9. "Modifications" means any addition to or deletion from the
+         substance or structure of either the Original Code or any previous
+         Modifications. When Covered Code is released as a series of files, a
+         Modification is:
+              A. Any addition to or deletion from the contents of a file
+              containing Original Code or previous Modifications.
+
+              B. Any new file that contains any part of the Original Code or
+              previous Modifications.
+
+         1.10. "Original Code" means Source Code of computer software code
+         which is described in the Source Code notice required by Exhibit A as
+         Original Code, and which, at the time of its release under this
+         License is not already Covered Code governed by this License.
+
+         1.10.1. "Patent Claims" means any patent claim(s), now owned or
+         hereafter acquired, including without limitation,  method, process,
+         and apparatus claims, in any patent Licensable by grantor.
+
+         1.11. "Source Code" means the preferred form of the Covered Code for
+         making modifications to it, including all modules it contains, plus
+         any associated interface definition files, scripts used to control
+         compilation and installation of an Executable, or source code
+         differential comparisons against either the Original Code or another
+         well known, available Covered Code of the Contributor's choice. The
+         Source Code can be in a compressed or archival form, provided the
+         appropriate decompression or de-archiving software is widely available
+         for no charge.
+
+         1.12. "You" (or "Your")  means an individual or a legal entity
+         exercising rights under, and complying with all of the terms of, this
+         License or a future version of this License issued under Section 6.1.
+         For legal entities, "You" includes any entity which controls, is
+         controlled by, or is under common control with You. For purposes of
+         this definition, "control" means (a) the power, direct or indirect,
+         to cause the direction or management of such entity, whether by
+         contract or otherwise, or (b) ownership of more than fifty percent
+         (50%) of the outstanding shares or beneficial ownership of such
+         entity.
+
+    2. Source Code License.
+
+         2.1. The Initial Developer Grant.
+         The Initial Developer hereby grants You a world-wide, royalty-free,
+         non-exclusive license, subject to third party intellectual property
+         claims:
+              (a)  under intellectual property rights (other than patent or
+              trademark) Licensable by Initial Developer to use, reproduce,
+              modify, display, perform, sublicense and distribute the Original
+              Code (or portions thereof) with or without Modifications, and/or
+              as part of a Larger Work; and
+
+              (b) under Patents Claims infringed by the making, using or
+              selling of Original Code, to make, have made, use, practice,
+              sell, and offer for sale, and/or otherwise dispose of the
+              Original Code (or portions thereof).
+
+              (c) the licenses granted in this Section 2.1(a) and (b) are
+              effective on the date Initial Developer first distributes
+              Original Code under the terms of this License.
+
+              (d) Notwithstanding Section 2.1(b) above, no patent license is
+              granted: 1) for code that You delete from the Original Code; 2)
+              separate from the Original Code;  or 3) for infringements caused
+              by: i) the modification of the Original Code or ii) the
+              combination of the Original Code with other software or devices.
+
+         2.2. Contributor Grant.
+         Subject to third party intellectual property claims, each Contributor
+         hereby grants You a world-wide, royalty-free, non-exclusive license
+
+              (a)  under intellectual property rights (other than patent or
+              trademark) Licensable by Contributor, to use, reproduce, modify,
+              display, perform, sublicense and distribute the Modifications
+              created by such Contributor (or portions thereof) either on an
+              unmodified basis, with other Modifications, as Covered Code
+              and/or as part of a Larger Work; and
+
+              (b) under Patent Claims infringed by the making, using, or
+              selling of  Modifications made by that Contributor either alone
+              and/or in combination with its Contributor Version (or portions
+              of such combination), to make, use, sell, offer for sale, have
+              made, and/or otherwise dispose of: 1) Modifications made by that
+              Contributor (or portions thereof); and 2) the combination of
+              Modifications made by that Contributor with its Contributor
+              Version (or portions of such combination).
+
+              (c) the licenses granted in Sections 2.2(a) and 2.2(b) are
+              effective on the date Contributor first makes Commercial Use of
+              the Covered Code.
+
+              (d)    Notwithstanding Section 2.2(b) above, no patent license is
+              granted: 1) for any code that Contributor has deleted from the
+              Contributor Version; 2)  separate from the Contributor Version;
+              3)  for infringements caused by: i) third party modifications of
+              Contributor Version or ii)  the combination of Modifications made
+              by that Contributor with other software  (except as part of the
+              Contributor Version) or other devices; or 4) under Patent Claims
+              infringed by Covered Code in the absence of Modifications made by
+              that Contributor.
+
+    3. Distribution Obligations.
+
+         3.1. Application of License.
+         The Modifications which You create or to which You contribute are
+         governed by the terms of this License, including without limitation
+         Section 2.2. The Source Code version of Covered Code may be
+         distributed only under the terms of this License or a future version
+         of this License released under Section 6.1, and You must include a
+         copy of this License with every copy of the Source Code You
+         distribute. You may not offer or impose any terms on any Source Code
+         version that alters or restricts the applicable version of this
+         License or the recipients' rights hereunder. However, You may include
+         an additional document offering the additional rights described in
+         Section 3.5.
+
+         3.2. Availability of Source Code.
+         Any Modification which You create or to which You contribute must be
+         made available in Source Code form under the terms of this License
+         either on the same media as an Executable version or via an accepted
+         Electronic Distribution Mechanism to anyone to whom you made an
+         Executable version available; and if made available via Electronic
+         Distribution Mechanism, must remain available for at least twelve (12)
+         months after the date it initially became available, or at least six
+         (6) months after a subsequent version of that particular Modification
+         has been made available to such recipients. You are responsible for
+         ensuring that the Source Code version remains available even if the
+         Electronic Distribution Mechanism is maintained by a third party.
+
+         3.3. Description of Modifications.
+         You must cause all Covered Code to which You contribute to contain a
+         file documenting the changes You made to create that Covered Code and
+         the date of any change. You must include a prominent statement that
+         the Modification is derived, directly or indirectly, from Original
+         Code provided by the Initial Developer and including the name of the
+         Initial Developer in (a) the Source Code, and (b) in any notice in an
+         Executable version or related documentation in which You describe the
+         origin or ownership of the Covered Code.
+
+         3.4. Intellectual Property Matters
+              (a) Third Party Claims.
+              If Contributor has knowledge that a license under a third party's
+              intellectual property rights is required to exercise the rights
+              granted by such Contributor under Sections 2.1 or 2.2,
+              Contributor must include a text file with the Source Code
+              distribution titled "LEGAL" which describes the claim and the
+              party making the claim in sufficient detail that a recipient will
+              know whom to contact. If Contributor obtains such knowledge after
+              the Modification is made available as described in Section 3.2,
+              Contributor shall promptly modify the LEGAL file in all copies
+              Contributor makes available thereafter and shall take other steps
+              (such as notifying appropriate mailing lists or newsgroups)
+              reasonably calculated to inform those who received the Covered
+              Code that new knowledge has been obtained.
+
+              (b) Contributor APIs.
+              If Contributor's Modifications include an application programming
+              interface and Contributor has knowledge of patent licenses which
+              are reasonably necessary to implement that API, Contributor must
+              also include this information in the LEGAL file.
+
+                   (c)    Representations.
+              Contributor represents that, except as disclosed pursuant to
+              Section 3.4(a) above, Contributor believes that Contributor's
+              Modifications are Contributor's original creation(s) and/or
+              Contributor has sufficient rights to grant the rights conveyed by
+              this License.
+
+         3.5. Required Notices.
+         You must duplicate the notice in Exhibit A in each file of the Source
+         Code.  If it is not possible to put such notice in a particular Source
+         Code file due to its structure, then You must include such notice in a
+         location (such as a relevant directory) where a user would be likely
+         to look for such a notice.  If You created one or more Modification(s)
+         You may add your name as a Contributor to the notice described in
+         Exhibit A.  You must also duplicate this License in any documentation
+         for the Source Code where You describe recipients' rights or ownership
+         rights relating to Covered Code.  You may choose to offer, and to
+         charge a fee for, warranty, support, indemnity or liability
+         obligations to one or more recipients of Covered Code. However, You
+         may do so only on Your own behalf, and not on behalf of the Initial
+         Developer or any Contributor. You must make it absolutely clear than
+         any such warranty, support, indemnity or liability obligation is
+         offered by You alone, and You hereby agree to indemnify the Initial
+         Developer and every Contributor for any liability incurred by the
+         Initial Developer or such Contributor as a result of warranty,
+         support, indemnity or liability terms You offer.
+
+         3.6. Distribution of Executable Versions.
+         You may distribute Covered Code in Executable form only if the
+         requirements of Section 3.1-3.5 have been met for that Covered Code,
+         and if You include a notice stating that the Source Code version of
+         the Covered Code is available under the terms of this License,
+         including a description of how and where You have fulfilled the
+         obligations of Section 3.2. The notice must be conspicuously included
+         in any notice in an Executable version, related documentation or
+         collateral in which You describe recipients' rights relating to the
+         Covered Code. You may distribute the Executable version of Covered
+         Code or ownership rights under a license of Your choice, which may
+         contain terms different from this License, provided that You are in
+         compliance with the terms of this License and that the license for the
+         Executable version does not attempt to limit or alter the recipient's
+         rights in the Source Code version from the rights set forth in this
+         License. If You distribute the Executable version under a different
+         license You must make it absolutely clear that any terms which differ
+         from this License are offered by You alone, not by the Initial
+         Developer or any Contributor. You hereby agree to indemnify the
+         Initial Developer and every Contributor for any liability incurred by
+         the Initial Developer or such Contributor as a result of any such
+         terms You offer.
+
+         3.7. Larger Works.
+         You may create a Larger Work by combining Covered Code with other code
+         not governed by the terms of this License and distribute the Larger
+         Work as a single product. In such a case, You must make sure the
+         requirements of this License are fulfilled for the Covered Code.
+
+    4. Inability to Comply Due to Statute or Regulation.
+
+         If it is impossible for You to comply with any of the terms of this
+         License with respect to some or all of the Covered Code due to
+         statute, judicial order, or regulation then You must: (a) comply with
+         the terms of this License to the maximum extent possible; and (b)
+         describe the limitations and the code they affect. Such description
+         must be included in the LEGAL file described in Section 3.4 and must
+         be included with all distributions of the Source Code. Except to the
+         extent prohibited by statute or regulation, such description must be
+         sufficiently detailed for a recipient of ordinary skill to be able to
+         understand it.
+
+    5. Application of this License.
+
+         This License applies to code to which the Initial Developer has
+         attached the notice in Exhibit A and to related Covered Code.
+
+    6. Versions of the License.
+
+         6.1. New Versions.
+         Netscape Communications Corporation ("Netscape") may publish revised
+         and/or new versions of the License from time to time. Each version
+         will be given a distinguishing version number.
+
+         6.2. Effect of New Versions.
+         Once Covered Code has been published under a particular version of the
+         License, You may always continue to use it under the terms of that
+         version. You may also choose to use such Covered Code under the terms
+         of any subsequent version of the License published by Netscape. No one
+         other than Netscape has the right to modify the terms applicable to
+         Covered Code created under this License.
+
+         6.3. Derivative Works.
+         If You create or use a modified version of this License (which you may
+         only do in order to apply it to code which is not already Covered Code
+         governed by this License), You must (a) rename Your license so that
+         the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape",
+         "MPL", "NPL" or any confusingly similar phrase do not appear in your
+         license (except to note that your license differs from this License)
+         and (b) otherwise make it clear that Your version of the license
+         contains terms which differ from the Mozilla Public License and
+         Netscape Public License. (Filling in the name of the Initial
+         Developer, Original Code or Contributor in the notice described in
+         Exhibit A shall not of themselves be deemed to be modifications of
+         this License.)
+
+    7. DISCLAIMER OF WARRANTY.
+
+         COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
+         WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+         WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF
+         DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
+         THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE
+         IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT,
+         YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE
+         COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER
+         OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
+         ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
+
+    8. TERMINATION.
+
+         8.1.  This License and the rights granted hereunder will terminate
+         automatically if You fail to comply with terms herein and fail to cure
+         such breach within 30 days of becoming aware of the breach. All
+         sublicenses to the Covered Code which are properly granted shall
+         survive any termination of this License. Provisions which, by their
+         nature, must remain in effect beyond the termination of this License
+         shall survive.
+
+         8.2.  If You initiate litigation by asserting a patent infringement
+         claim (excluding declatory judgment actions) against Initial Developer
+         or a Contributor (the Initial Developer or Contributor against whom
+         You file such action is referred to as "Participant")  alleging that:
+
+         (a)  such Participant's Contributor Version directly or indirectly
+         infringes any patent, then any and all rights granted by such
+         Participant to You under Sections 2.1 and/or 2.2 of this License
+         shall, upon 60 days notice from Participant terminate prospectively,
+         unless if within 60 days after receipt of notice You either: (i)
+         agree in writing to pay Participant a mutually agreeable reasonable
+         royalty for Your past and future use of Modifications made by such
+         Participant, or (ii) withdraw Your litigation claim with respect to
+         the Contributor Version against such Participant.  If within 60 days
+         of notice, a reasonable royalty and payment arrangement are not
+         mutually agreed upon in writing by the parties or the litigation claim
+         is not withdrawn, the rights granted by Participant to You under
+         Sections 2.1 and/or 2.2 automatically terminate at the expiration of
+         the 60 day notice period specified above.
+
+         (b)  any software, hardware, or device, other than such Participant's
+         Contributor Version, directly or indirectly infringes any patent, then
+         any rights granted to You by such Participant under Sections 2.1(b)
+         and 2.2(b) are revoked effective as of the date You first made, used,
+         sold, distributed, or had made, Modifications made by that
+         Participant.
+
+         8.3.  If You assert a patent infringement claim against Participant
+         alleging that such Participant's Contributor Version directly or
+         indirectly infringes any patent where such claim is resolved (such as
+         by license or settlement) prior to the initiation of patent
+         infringement litigation, then the reasonable value of the licenses
+         granted by such Participant under Sections 2.1 or 2.2 shall be taken
+         into account in determining the amount or value of any payment or
+         license.
+
+         8.4.  In the event of termination under Sections 8.1 or 8.2 above,
+         all end user license agreements (excluding distributors and resellers)
+         which have been validly granted by You or any distributor hereunder
+         prior to termination shall survive termination.
+
+    9. LIMITATION OF LIABILITY.
+
+         UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+         (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL
+         DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE,
+         OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR
+         ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY
+         CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL,
+         WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
+         COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
+         INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+         LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
+         RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+         PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE
+         EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO
+         THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
+
+    10. U.S. GOVERNMENT END USERS.
+
+         The Covered Code is a "commercial item," as that term is defined in
+         48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer
+         software" and "commercial computer software documentation," as such
+         terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48
+         C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995),
+         all U.S. Government End Users acquire Covered Code with only those
+         rights set forth herein.
+
+    11. MISCELLANEOUS.
+
+         This License represents the complete agreement concerning subject
+         matter hereof. If any provision of this License is held to be
+         unenforceable, such provision shall be reformed only to the extent
+         necessary to make it enforceable. This License shall be governed by
+         California law provisions (except to the extent applicable law, if
+         any, provides otherwise), excluding its conflict-of-law provisions.
+         With respect to disputes in which at least one party is a citizen of,
+         or an entity chartered or registered to do business in the United
+         States of America, any litigation relating to this License shall be
+         subject to the jurisdiction of the Federal Courts of the Northern
+         District of California, with venue lying in Santa Clara County,
+         California, with the losing party responsible for costs, including
+         without limitation, court costs and reasonable attorneys' fees and
+         expenses. The application of the United Nations Convention on
+         Contracts for the International Sale of Goods is expressly excluded.
+         Any law or regulation which provides that the language of a contract
+         shall be construed against the drafter shall not apply to this
+         License.
+
+    12. RESPONSIBILITY FOR CLAIMS.
+
+         As between Initial Developer and the Contributors, each party is
+         responsible for claims and damages arising, directly or indirectly,
+         out of its utilization of rights under this License and You agree to
+         work with Initial Developer and Contributors to distribute such
+         responsibility on an equitable basis. Nothing herein is intended or
+         shall be deemed to constitute any admission of liability.
+
+    13. MULTIPLE-LICENSED CODE.
+
+         Initial Developer may designate portions of the Covered Code as
+         "Multiple-Licensed".  "Multiple-Licensed" means that the Initial
+         Developer permits you to utilize portions of the Covered Code under
+         Your choice of the NPL or the alternative licenses, if any, specified
+         by the Initial Developer in the file described in Exhibit A.
+
+    EXHIBIT A -Mozilla Public License.
+
+         ``The contents of this file are subject to the Mozilla Public License
+         Version 1.1 (the "License"); you may not use this file except in
+         compliance with the License. You may obtain a copy of the License at
+         http://www.mozilla.org/MPL/
+
+         Software distributed under the License is distributed on an "AS IS"
+         basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+         License for the specific language governing rights and limitations
+         under the License.
+
+         The Original Code is ______________________________________.
+
+         The Initial Developer of the Original Code is ________________________.
+         Portions created by ______________________ are Copyright (C) ______
+         _______________________. All Rights Reserved.
+
+         Contributor(s): ______________________________________.
+
+         Alternatively, the contents of this file may be used under the terms
+         of the _____ license (the  "[___] License"), in which case the
+         provisions of [______] License are applicable instead of those
+         above.  If you wish to allow use of your version of this file only
+         under the terms of the [____] License and not to allow others to use
+         your version of this file under the MPL, indicate your decision by
+         deleting  the provisions above and replace  them with the notice and
+         other provisions required by the [___] License.  If you do not delete
+         the provisions above, a recipient may use your version of this file
+         under either the MPL or the [___] License."
+
+         [NOTE: The text of this Exhibit A may differ slightly from the text of
+         the notices in the Source Code files of the Original Code. You should
+         use the text of this Exhibit A rather than the text found in the
+         Original Code Source Code for Your Modifications.]
diff --git a/jackrabbit-standalone/src/main/java/org/apache/jackrabbit/standalone/cli/ext/ConnectToJNDIServer.java b/jackrabbit-standalone/src/main/java/org/apache/jackrabbit/standalone/cli/ext/ConnectToJNDIServer.java
index 7f14c0e..d5c1190 100644
--- a/jackrabbit-standalone/src/main/java/org/apache/jackrabbit/standalone/cli/ext/ConnectToJNDIServer.java
+++ b/jackrabbit-standalone/src/main/java/org/apache/jackrabbit/standalone/cli/ext/ConnectToJNDIServer.java
@@ -1,71 +1,71 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.standalone.cli.ext;
-
-import javax.jcr.Repository;
-import javax.naming.InitialContext;
-
-import org.apache.commons.chain.Command;
-import org.apache.commons.chain.Context;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.rmi.client.ClientAdapterFactory;
-import org.apache.jackrabbit.rmi.remote.RemoteRepository;
-import org.apache.jackrabbit.standalone.cli.CommandHelper;
-
-/**
- * Connect to a JCR-RMI server
- */
-public class ConnectToJNDIServer implements Command {
-	/** logger */
-	private static Log log = LogFactory.getLog(ConnectToJNDIServer.class);
-
-	// ---------------------------- < keys >
-	/** url key */
-	private String urlKey = "url";
-
-	/**
-	 * {@inheritDoc}
-	 */
-	public boolean execute(Context ctx) throws Exception {
-		String url = (String) ctx.get(this.urlKey);
-		if (log.isDebugEnabled()) {
-			log.debug("connecting to jndi server at " + url);
-		}
-		InitialContext iCtx = new InitialContext();
-		ClientAdapterFactory adapter = new ClientAdapterFactory();
-		RemoteRepository remote = (RemoteRepository) iCtx.lookup(url);
-		Repository repo = adapter.getRepository(remote);
-		CommandHelper.setRepository(ctx, repo, "jndi " + url);
-		return false;
-	}
-
-	/**
-	 * @return the url key
-	 */
-	public String getUrlKey() {
-		return urlKey;
-	}
-
-	/**
-	 * @param urlKey
-	 *            the url key to set
-	 */
-	public void setUrlKey(String urlKey) {
-		this.urlKey = urlKey;
-	}
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.standalone.cli.ext;
+
+import javax.jcr.Repository;
+import javax.naming.InitialContext;
+
+import org.apache.commons.chain.Command;
+import org.apache.commons.chain.Context;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.rmi.client.ClientAdapterFactory;
+import org.apache.jackrabbit.rmi.remote.RemoteRepository;
+import org.apache.jackrabbit.standalone.cli.CommandHelper;
+
+/**
+ * Connect to a JCR-RMI server
+ */
+public class ConnectToJNDIServer implements Command {
+	/** logger */
+	private static Log log = LogFactory.getLog(ConnectToJNDIServer.class);
+
+	// ---------------------------- < keys >
+	/** url key */
+	private String urlKey = "url";
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public boolean execute(Context ctx) throws Exception {
+		String url = (String) ctx.get(this.urlKey);
+		if (log.isDebugEnabled()) {
+			log.debug("connecting to jndi server at " + url);
+		}
+		InitialContext iCtx = new InitialContext();
+		ClientAdapterFactory adapter = new ClientAdapterFactory();
+		RemoteRepository remote = (RemoteRepository) iCtx.lookup(url);
+		Repository repo = adapter.getRepository(remote);
+		CommandHelper.setRepository(ctx, repo, "jndi " + url);
+		return false;
+	}
+
+	/**
+	 * @return the url key
+	 */
+	public String getUrlKey() {
+		return urlKey;
+	}
+
+	/**
+	 * @param urlKey
+	 *            the url key to set
+	 */
+	public void setUrlKey(String urlKey) {
+		this.urlKey = urlKey;
+	}
+}
diff --git a/jackrabbit-webapp/README.txt b/jackrabbit-webapp/README.txt
index 17432ce..c35e1bb 100644
--- a/jackrabbit-webapp/README.txt
+++ b/jackrabbit-webapp/README.txt
@@ -12,11 +12,11 @@ This component provides servlets used to access a Jackrabbit repository:
 In addition, the project contains 2 different WebDAV servlets:
 
     * SimpleWebdavServlet.java
-      Adds webdav support (DAV 1,2) to your jackrabbit repository.
+      Adds WebDAV support (DAV 1,2) to your jackrabbit repository.
   
     * JCRWebdavServerServlet.java
-      A servlet used to remove JSR170 calls via webDAV. 
+      A servlet used to remote JSR170 calls via WebDAV. 
       IMPORTANT: Please note, that this servlet is not intended to provide 
-      common webdav support to the repository. Instead the primary goal is to 
+      common WebDAV support to the repository. Instead the primary goal is to 
       remote JSR170 calls.
-      For the corresponding client see -> contrib/spi (work in progress).
+      For the corresponding client see -> jackrabbit-jcr2dav (work in progress).
diff --git a/jackrabbit-webapp/pom.xml b/jackrabbit-webapp/pom.xml
index 05f08e6..43c2388 100644
--- a/jackrabbit-webapp/pom.xml
+++ b/jackrabbit-webapp/pom.xml
@@ -26,7 +26,7 @@
   <parent>
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>jackrabbit-parent</artifactId>
-    <version>2.3.6</version>
+    <version>2.10.1</version>
     <relativePath>../jackrabbit-parent/pom.xml</relativePath>
   </parent>
   <artifactId>jackrabbit-webapp</artifactId>
@@ -35,7 +35,8 @@
   <description>Web application that hosts and serves a Jackrabbit content repository</description>
 
   <properties>
-    <tomcat.version>6.0.29</tomcat.version>
+    <oak.version>1.0.12</oak.version>
+    <tomcat.version>7.0.40</tomcat.version>
   </properties>
 
   <dependencies>
@@ -46,7 +47,7 @@
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-core</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.tika</groupId>
@@ -65,17 +66,27 @@
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-server</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-servlet</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.jackrabbit</groupId>
       <artifactId>jackrabbit-jcr-rmi</artifactId>
-      <version>2.3.6</version>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jackrabbit</groupId>
+      <artifactId>oak-jcr</artifactId>
+      <version>${oak.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jackrabbit</groupId>
+      <artifactId>oak-upgrade</artifactId>
+      <version>${oak.version}</version>
     </dependency>
     <dependency>
       <groupId>ch.qos.logback</groupId>
@@ -83,10 +94,15 @@
     </dependency>
     <dependency>
       <groupId>org.apache.tomcat</groupId>
-      <artifactId>servlet-api</artifactId>
+      <artifactId>tomcat-servlet-api</artifactId>
       <version>${tomcat.version}</version>
       <scope>provided</scope>
     </dependency>
+    <dependency>
+      <groupId>com.googlecode.json-simple</groupId>
+      <artifactId>json-simple</artifactId>
+      <version>1.1</version>
+    </dependency>
 
     <dependency>
       <groupId>junit</groupId>
@@ -95,19 +111,19 @@
     </dependency>
     <dependency>
       <groupId>org.apache.tomcat</groupId>
-      <artifactId>catalina</artifactId>
+      <artifactId>tomcat-catalina</artifactId>
       <version>${tomcat.version}</version>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.tomcat</groupId>
-      <artifactId>coyote</artifactId>
+      <artifactId>tomcat-coyote</artifactId>
       <version>${tomcat.version}</version>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.tomcat</groupId>
-      <artifactId>jasper</artifactId>
+      <artifactId>tomcat-jasper</artifactId>
       <version>${tomcat.version}</version>
       <scope>test</scope>
     </dependency>
@@ -201,11 +217,12 @@
         <plugin>
           <artifactId>maven-failsafe-plugin</artifactId>
           <configuration>
-            <workingDirectory>${project.build.directory}</workingDirectory>
             <systemPropertyVariables>
-              <webapp.directory>${project.build.directory}/${project.artifactId}-${project.version}</webapp.directory>
-              <default.webxml>${basedir}/src/test/resources/default-web.xml</default.webxml>
+              <derby.stream.error.file>target/derby.log</derby.stream.error.file>
             </systemPropertyVariables>
+            <excludes>
+                <exclude>**/BackwardsCompatibilityIT.java</exclude> <!-- JCR-3640 -->
+            </excludes>
           </configuration>
         </plugin>
       </plugins>
diff --git a/jackrabbit-webapp/src/main/java/org/apache/jackrabbit/j2ee/Installer.java b/jackrabbit-webapp/src/main/java/org/apache/jackrabbit/j2ee/Installer.java
index f7899b3..6a9104f 100644
--- a/jackrabbit-webapp/src/main/java/org/apache/jackrabbit/j2ee/Installer.java
+++ b/jackrabbit-webapp/src/main/java/org/apache/jackrabbit/j2ee/Installer.java
@@ -125,16 +125,26 @@ public class Installer {
     public int installRepository(HttpServletRequest req)
             throws ServletException, IOException {
         String repHome = req.getParameter("repository_home");
+        String repType = req.getParameter("repository_type");
         String repXml = req.getParameter("repository_xml");
         String mode = req.getParameter("mode");
+
         if (repHome == null || mode == null) {
             return C_INVALID_INPUT;
         }
-        if (repXml == null || repXml.length() == 0) {
-            repXml = repHome + "/repository.xml";
-        }
         File home = new File(repHome);
-        File config = new File(repXml);
+
+        File config;
+        if ("oak".equals(repType)) {
+            config = null;
+            repXml = null;
+        } else if (repXml == null || repXml.length() == 0) {
+            config = new File(home, "repository.xml");
+            repXml = config.getPath();
+        } else {
+            config = new File(repXml);
+        }
+
         if ("new".equals(mode)) {
             // Test internal folder repository existence and not home because home is already created
             // by org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet
@@ -142,25 +152,28 @@ public class Installer {
                 log.error("Trying to install new repository home '{}' but it already contain a repository", repHome);
                 return C_HOME_EXISTS;
             }
-            if (config.exists()) {
+            if (config != null && config.exists()) {
                 log.error("Trying to install new repository config '{}' but already exists", repXml);
                 return C_CONFIG_EXISTS;
             }
             log.info("Creating new repository home '{}'", repHome);
             home.mkdirs();
-            // install repository xml
-            try {
-                installRepositoryConfig(config);
-            } catch (IOException e) {
-                log.error("Error while installing new repository config '{}': {}", repXml, e.toString());
-                return C_BOOTSTRAP_EXISTS;
+
+            if (config != null) {
+                // install repository xml for Jackrabbit Classic
+                try {
+                    installRepositoryConfig(config);
+                } catch (IOException e) {
+                    log.error("Error while installing new repository config '{}': {}", repXml, e.toString());
+                    return C_BOOTSTRAP_EXISTS;
+                }
             }
         } else {
             if (!home.exists()) {
-                log.error("Trying to use exisintg repository home '{}' but does not exists", repHome);
+                log.error("Trying to use existing repository home '{}' but does not exists", repHome);
                 return C_HOME_MISSING;
             }
-            if (!config.exists()) {
+            if (config != null && !config.exists()) {
                 log.error("Trying to use existing repository config '{}' but does not exists", repXml);
                 return C_CONFIG_MISSING;
             }
@@ -210,7 +223,9 @@ public class Installer {
         Properties props = new Properties();
         props.load(in);
         props.setProperty("repository.home", repHome);
-        props.setProperty("repository.config", repXml);
+        if (repXml != null) {
+            props.setProperty("repository.config", repXml);
+        }
         in.close();
         if (!dest.getParentFile().exists()) {
             dest.getParentFile().mkdirs();
diff --git a/jackrabbit-webapp/src/main/java/org/apache/jackrabbit/j2ee/JcrApiNotFoundException.java b/jackrabbit-webapp/src/main/java/org/apache/jackrabbit/j2ee/JcrApiNotFoundException.java
index f41478e..e70db5b 100644
--- a/jackrabbit-webapp/src/main/java/org/apache/jackrabbit/j2ee/JcrApiNotFoundException.java
+++ b/jackrabbit-webapp/src/main/java/org/apache/jackrabbit/j2ee/JcrApiNotFoundException.java
@@ -1,38 +1,38 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.j2ee;
-
-/**
- * Exception for signaling that the JCR API is not available.
- */
-public class JcrApiNotFoundException extends ServletExceptionWithCause {
-
-    /**
-     * Serial version UID
-     */
-    private static final long serialVersionUID = -6439777923943394980L;
-
-    /**
-     * Creates an exception to signal that the JCR API is not available.
-     *
-     * @param e the specific exception that indicates the lack of the JCR API
-     */
-    public JcrApiNotFoundException(ClassNotFoundException e) {
-        super("JCR API (jcr-1.0.jar) not available in the classpath", e);
-    }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.j2ee;
+
+/**
+ * Exception for signaling that the JCR API is not available.
+ */
+public class JcrApiNotFoundException extends ServletExceptionWithCause {
+
+    /**
+     * Serial version UID
+     */
+    private static final long serialVersionUID = -6439777923943394980L;
+
+    /**
+     * Creates an exception to signal that the JCR API is not available.
+     *
+     * @param e the specific exception that indicates the lack of the JCR API
+     */
+    public JcrApiNotFoundException(ClassNotFoundException e) {
+        super("JCR API (jcr-1.0.jar) not available in the classpath", e);
+    }
+
+}
diff --git a/jackrabbit-webapp/src/main/java/org/apache/jackrabbit/j2ee/RepositoryAccessServlet.java b/jackrabbit-webapp/src/main/java/org/apache/jackrabbit/j2ee/RepositoryAccessServlet.java
index 8871359..f09575f 100644
--- a/jackrabbit-webapp/src/main/java/org/apache/jackrabbit/j2ee/RepositoryAccessServlet.java
+++ b/jackrabbit-webapp/src/main/java/org/apache/jackrabbit/j2ee/RepositoryAccessServlet.java
@@ -59,6 +59,11 @@ public class RepositoryAccessServlet extends HttpServlet {
     private final static String CTX_PARAM_THIS = "repository.access.servlet";
 
     /**
+     * Ugly hack to override the bootstrap file location in the test cases
+     */
+    static String bootstrapOverride = null;
+
+    /**
      * the bootstrap config
      */
     private BootstrapConfig config;
@@ -122,7 +127,10 @@ public class RepositoryAccessServlet extends HttpServlet {
         if (config == null) {
             // check if there is a loadable bootstrap config
             Properties bootstrapProps = new Properties();
-            String bstrp = getServletConfig().getInitParameter(INIT_PARAM_BOOTSTRAP_CONFIG);
+            String bstrp = bootstrapOverride;
+            if (bstrp == null) {
+                bstrp = getServletConfig().getInitParameter(INIT_PARAM_BOOTSTRAP_CONFIG);
+            }
             if (bstrp != null) {
                 // check if it's a web-resource
                 InputStream in = getServletContext().getResourceAsStream(bstrp);
diff --git a/jackrabbit-webapp/src/main/java/org/apache/jackrabbit/j2ee/RepositoryStartupServlet.java b/jackrabbit-webapp/src/main/java/org/apache/jackrabbit/j2ee/RepositoryStartupServlet.java
index ca04989..28512ad 100644
--- a/jackrabbit-webapp/src/main/java/org/apache/jackrabbit/j2ee/RepositoryStartupServlet.java
+++ b/jackrabbit-webapp/src/main/java/org/apache/jackrabbit/j2ee/RepositoryStartupServlet.java
@@ -20,6 +20,10 @@ import org.apache.jackrabbit.api.JackrabbitRepository;
 import org.apache.jackrabbit.commons.repository.RepositoryFactory;
 import org.apache.jackrabbit.core.RepositoryImpl;
 import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.apache.jackrabbit.oak.jcr.Jcr;
+import org.apache.jackrabbit.oak.plugins.segment.SegmentNodeStore;
+import org.apache.jackrabbit.oak.plugins.segment.SegmentStore;
+import org.apache.jackrabbit.oak.plugins.segment.file.FileStore;
 import org.apache.jackrabbit.rmi.server.RemoteAdapterFactory;
 import org.apache.jackrabbit.rmi.server.ServerAdapterFactory;
 import org.apache.jackrabbit.servlet.AbstractRepositoryServlet;
@@ -60,7 +64,7 @@ import javax.servlet.http.HttpServletResponse;
  * to the JNDI environment and optional to the RMI registry.
  * <p id="registerAlgo">
  * <b>Registration with RMI</b>
- * <p/>
+ * <p>
  * Upon successfull creation of the repository in the {@link #init()} method,
  * the repository is registered with an RMI registry if the web application is
  * so configured. To register with RMI, the following web application
@@ -71,13 +75,13 @@ import javax.servlet.http.HttpServletResponse;
  * repository is to be bound in the registry, and <code>rmi-uri</code>
  * designating an RMI URI complete with host, optional port and name to which
  * the object is bound.
- * <p/>
+ * <p>
  * If the <code>rmi-uri</code> parameter is configured with a non-empty value,
  * the <code>rmi-port</code> and <code>rmi-host</code> parameters are ignored.
  * The <code>repository-name</code> parameter is only considered if a non-empty
  * <code>rmi-uri</code> parameter is configured if the latter does not contain
  * a name to which to bind the repository.
- * <p/>
+ * <p>
  * This is the algorithm used to find out the host, port and name for RMI
  * registration:
  * <ol>
@@ -100,7 +104,7 @@ import javax.servlet.http.HttpServletResponse;
  * zero or a negative value, the default port for the RMI registry
  * (<code>1099</code>) is used.
  * </ol>
- * <p/>
+ * <p>
  * After finding the host and port of the registry, the RMI registry itself
  * is acquired. It is assumed, that host and port primarily designate an RMI
  * registry, which should be active on the local host but has not been started
@@ -110,11 +114,11 @@ import javax.servlet.http.HttpServletResponse;
  * method is called to get a remote instance of the registry. Note, that
  * <code>getRegistry</code> does not create an actual registry on the given
  * host/port nor does it check, whether an RMI registry is active.
- * <p/>
+ * <p>
  * When the registry has been retrieved, either by creation or by just creating
  * a remote instance, the repository is bound to the configured name in the
  * registry.
- * <p/>
+ * <p>
  * Possible causes for registration failures include:
  * <ul>
  * <li>The web application is not configured to register with an RMI registry at
@@ -127,13 +131,13 @@ import javax.servlet.http.HttpServletResponse;
  * <li>An object may already be bound to the same name as is configured to be
  * used for the repository.
  * </ul>
- * <p/>
+ * <p>
  * <b>Note:</b> if a <code>bootstrap-config</code> init parameter is specified the
  * servlet tries to read the respective resource, either as context resource or
  * as file. The properties specified in this file override the init params
  * specified in the <code>web.xml</code>.
- * <p/>
- * <p/>
+ * <p>
+ * <p>
  * <b>Setup Wizard Functionality</b><br>
  * When using the first time, the configuraition can miss the relevant
  * repository parameters in the web.xml. if so, it must contain a
@@ -163,6 +167,16 @@ public class RepositoryStartupServlet extends AbstractRepositoryServlet {
     public final static String INIT_PARAM_BOOTSTRAP_CONFIG = "bootstrap-config";
 
     /**
+     * Ugly hack to override the bootstrap file location in the test cases
+     */
+    static String bootstrapOverride = null;
+
+    /**
+     * the TarMK segment store
+     */
+    private SegmentStore store;
+
+    /**
      * the registered repository
      */
     private Repository repository;
@@ -324,7 +338,10 @@ public class RepositoryStartupServlet extends AbstractRepositoryServlet {
     private boolean configure() throws ServletException {
         // check if there is a loadable bootstrap config
         Properties bootstrapProps = new Properties();
-        String bstrp = getServletConfig().getInitParameter(INIT_PARAM_BOOTSTRAP_CONFIG);
+        String bstrp = bootstrapOverride;
+        if (bstrp == null) {
+            bstrp = getServletConfig().getInitParameter(INIT_PARAM_BOOTSTRAP_CONFIG);
+        }
         if (bstrp != null) {
             // check if it's a web-resource
             InputStream in = getServletContext().getResourceAsStream(bstrp);
@@ -362,8 +379,7 @@ public class RepositoryStartupServlet extends AbstractRepositoryServlet {
         config.init(bootstrapProps);
         config.validate();
         if (!config.isValid()
-                || config.getRepositoryHome() == null
-                || config.getRepositoryConfig() == null) {
+                || config.getRepositoryHome() == null) {
             if (bstrp == null) {
                 log.error("Repository startup configuration is not valid.");
             } else {
@@ -394,25 +410,35 @@ public class RepositoryStartupServlet extends AbstractRepositoryServlet {
                     "Repository configuration failure: " + config.getRepositoryHome(), e);
         }
         String repConfig = config.getRepositoryConfig();
-        InputStream in = getServletContext().getResourceAsStream(repConfig);
-        if (in == null) {
-            try {
-                in = new FileInputStream(new File(repConfig));
-            } catch (FileNotFoundException e) {
-                // fallback to old config
+        if (repConfig != null) { // Jackrabbit Classic
+            InputStream in = getServletContext().getResourceAsStream(repConfig);
+            if (in == null) {
                 try {
-                    in = new FileInputStream(new File(repHome, repConfig));
-                } catch (FileNotFoundException e1) {
-                    throw new ServletExceptionWithCause(
-                            "Repository configuration not found: " + repConfig, e);
+                    in = new FileInputStream(new File(repConfig));
+                } catch (FileNotFoundException e) {
+                    // fallback to old config
+                    try {
+                        in = new FileInputStream(new File(repHome, repConfig));
+                    } catch (FileNotFoundException e1) {
+                        throw new ServletExceptionWithCause(
+                                "Repository configuration not found: " + repConfig, e);
+                    }
                 }
             }
-        }
 
-        try {
-            repository = createRepository(new InputSource(in), repHome);
-        } catch (RepositoryException e) {
-            throw new ServletExceptionWithCause("Error while creating repository", e);
+            try {
+                repository = createRepository(new InputSource(in), repHome);
+            } catch (RepositoryException e) {
+                throw new ServletExceptionWithCause("Error while creating repository", e);
+            }
+        } else { // Jackrabbit Oak
+            try {
+                String model = System.getProperty("sun.arch.data.model", "32");
+                store = new FileStore(repHome, 256, "64".equals(model));
+                repository = new Jcr(new SegmentNodeStore(store)).createRepository();
+            } catch (IOException e) {
+                throw new ServletExceptionWithCause("Error while creating repository", e);
+            }
         }
     }
 
@@ -423,7 +449,10 @@ public class RepositoryStartupServlet extends AbstractRepositoryServlet {
      * <code>nulled</code>.
      */
     private void shutdownRepository() {
-        if (repository instanceof JackrabbitRepository) {
+        if (store != null) {
+            store.close();
+            store = null;
+        } else if (repository instanceof JackrabbitRepository) {
             ((JackrabbitRepository) repository).shutdown();
         }
         repository = null;
@@ -621,7 +650,7 @@ public class RepositoryStartupServlet extends AbstractRepositoryServlet {
      * Return the fully qualified name of the class providing the remote
      * repository. The class whose name is returned must implement the
      * {@link RemoteFactoryDelegater} interface.
-     * <p/>
+     * <p>
      * Subclasses may override this method for providing a name of a own
      * implementation.
      *
@@ -634,7 +663,7 @@ public class RepositoryStartupServlet extends AbstractRepositoryServlet {
     /**
      * Returns an <code>RMIServerSocketFactory</code> used to create the server
      * socket for a locally created RMI registry.
-     * <p/>
+     * <p>
      * This implementation returns a new instance of a simple
      * <code>RMIServerSocketFactory</code> which just creates instances of
      * the <code>java.net.ServerSocket</code> class bound to the given
diff --git a/jackrabbit-webapp/src/main/webapp/META-INF/LICENSE b/jackrabbit-webapp/src/main/webapp/META-INF/LICENSE
index e36ca30..82ea725 100644
--- a/jackrabbit-webapp/src/main/webapp/META-INF/LICENSE
+++ b/jackrabbit-webapp/src/main/webapp/META-INF/LICENSE
@@ -925,58 +925,701 @@ Array utility code in Lucene Java (lucene-core)
     ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
     OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-Commons Codec library (commons-codec-1.2.jar)
+AspectJ runtime library (aspectjrt)
 
-    ====================================================================
+    Eclipse Public License - v 1.0
 
-    The Apache Software License, Version 1.1
+    THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+    PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF
+    THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
 
-    Copyright (c) 2001-2004 The Apache Software Foundation.  All rights
-    reserved.
+    1. DEFINITIONS
 
-    Redistribution and use in source and binary forms, with or without
-    modification, are permitted provided that the following conditions
-    are met:
+    "Contribution" means:
 
-    1. Redistributions of source code must retain the above copyright
-       notice, this list of conditions and the following disclaimer.
+    a) in the case of the initial Contributor, the initial code and
+       documentation distributed under this Agreement, and
 
-    2. Redistributions in binary form must reproduce the above copyright
-       notice, this list of conditions and the following disclaimer in
-       the documentation and/or other materials provided with the
-       distribution.
-
-    3. The end-user documentation included with the redistribution,
-       if any, must include the following acknowledgement:
-          "This product includes software developed by the
-           Apache Software Foundation (http://www.apache.org/)."
-       Alternately, this acknowledgement may appear in the software itself,
-       if and wherever such third-party acknowledgements normally appear.
-
-    4. The names "Apache", "The Jakarta Project", "Commons", and "Apache Software
-       Foundation" must not be used to endorse or promote products derived
-       from this software without prior written permission. For written
-       permission, please contact apache at apache.org.
-
-    5. Products derived from this software may not be called "Apache"
-       nor may "Apache" appear in their name without prior
-       written permission of the Apache Software Foundation.
-
-    THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
-    WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-    DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
-    ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
-    USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-    OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-    OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-    SUCH DAMAGE.
-    ====================================================================
+    b) in the case of each subsequent Contributor:
+
+       i) changes to the Program, and
+
+       ii) additions to the Program;
+
+       where such changes and/or additions to the Program originate from and
+       are distributed by that particular Contributor. A Contribution
+       'originates' from a Contributor if it was added to the Program by
+       such Contributor itself or anyone acting on such Contributor's behalf.
+       Contributions do not include additions to the Program which: (i) are
+       separate modules of software distributed in conjunction with the
+       Program under their own license agreement, and (ii) are not derivative
+       works of the Program.
+
+    "Contributor" means any person or entity that distributes the Program.
+
+    "Licensed Patents " mean patent claims licensable by a Contributor which
+    are necessarily infringed by the use or sale of its Contribution alone or
+    when combined with the Program.
+
+    "Program" means the Contributions distributed in accordance with this
+    Agreement.
 
-    This software consists of voluntary contributions made by many
-    individuals on behalf of the Apache Software Foundation.  For more
-    information on the Apache Software Foundation, please see
-    <http://www.apache.org/>.
+    "Recipient" means anyone who receives the Program under this Agreement,
+    including all Contributors.
+
+    2. GRANT OF RIGHTS
+
+    a) Subject to the terms of this Agreement, each Contributor hereby grants
+       Recipient a non-exclusive, worldwide, royalty-free copyright license to
+       reproduce, prepare derivative works of, publicly display, publicly
+       perform, distribute and sublicense the Contribution of such
+       Contributor, if any, and such derivative works, in source code and
+       object code form.
+
+    b) Subject to the terms of this Agreement, each Contributor hereby grants
+       Recipient a non-exclusive, worldwide, royalty-free patent license under
+       Licensed Patents to make, use, sell, offer to sell, import and
+       otherwise transfer the Contribution of such Contributor, if any, in
+       source code and object code form. This patent license shall apply to
+       the combination of the Contribution and the Program if, at the time
+       the Contribution is added by the Contributor, such addition of the
+       Contribution causes such combination to be covered by the Licensed
+       Patents. The patent license shall not apply to any other combinations
+       which include the Contribution. No hardware per se is licensed hereunder.
+
+    c) Recipient understands that although each Contributor grants the
+       licenses to its Contributions set forth herein, no assurances are
+       provided by any Contributor that the Program does not infringe the
+       patent or other intellectual property rights of any other entity. Each
+       Contributor disclaims any liability to Recipient for claims brought by
+       any other entity based on infringement of intellectual property rights
+       or otherwise. As a condition to exercising the rights and licenses
+       granted hereunder, each Recipient hereby assumes sole responsibility
+       to secure any other intellectual property rights needed, if any. For
+       example, if a third party patent license is required to allow Recipient
+       to distribute the Program, it is Recipient's responsibility to acquire
+       that license before distributing the Program.
+
+    d) Each Contributor represents that to its knowledge it has sufficient
+       copyright rights in its Contribution, if any, to grant the copyright
+       license set forth in this Agreement.
+
+    3. REQUIREMENTS
+
+    A Contributor may choose to distribute the Program in object code form
+    under its own license agreement, provided that:
+
+    a) it complies with the terms and conditions of this Agreement; and
+
+    b) its license agreement:
+
+       i)   effectively disclaims on behalf of all Contributors all warranties
+            and conditions, express and implied, including warranties or
+            conditions of title and non-infringement, and implied warranties
+            or conditions of merchantability and fitness for a particular
+            purpose;
+
+       ii)  effectively excludes on behalf of all Contributors all liability
+            for damages, including direct, indirect, special, incidental and
+            consequential damages, such as lost profits;
+
+       iii) states that any provisions which differ from this Agreement are
+            offered by that Contributor alone and not by any other party; and
+
+       iv)  states that source code for the Program is available from such
+            Contributor, and informs licensees how to obtain it in a
+            reasonable manner on or through a medium customarily used for
+            software exchange.
+
+    When the Program is made available in source code form:
+
+    a) it must be made available under this Agreement; and
+
+    b) a copy of this Agreement must be included with each copy of the
+       Program.
+
+    Contributors may not remove or alter any copyright notices contained
+    within the Program.
+
+    Each Contributor must identify itself as the originator of its
+    Contribution, if any, in a manner that reasonably allows subsequent
+    Recipients to identify the originator of the Contribution.
+
+    4. COMMERCIAL DISTRIBUTION
+
+    Commercial distributors of software may accept certain responsibilities
+    with respect to end users, business partners and the like. While this
+    license is intended to facilitate the commercial use of the Program,
+    the Contributor who includes the Program in a commercial product offering
+    should do so in a manner which does not create potential liability for
+    other Contributors. Therefore, if a Contributor includes the Program in
+    a commercial product offering, such Contributor ("Commercial Contributor")
+    hereby agrees to defend and indemnify every other Contributor
+    ("Indemnified Contributor") against any losses, damages and costs
+    (collectively "Losses") arising from claims, lawsuits and other legal
+    actions brought by a third party against the Indemnified Contributor to
+    the extent caused by the acts or omissions of such Commercial Contributor
+    in connection with its distribution of the Program in a commercial
+    product offering. The obligations in this section do not apply to any
+    claims or Losses relating to any actual or alleged intellectual property
+    infringement. In order to qualify, an Indemnified Contributor must:
+    a) promptly notify the Commercial Contributor in writing of such claim,
+    and b) allow the Commercial Contributor to control, and cooperate with
+    the Commercial Contributor in, the defense and any related settlement
+    negotiations. The Indemnified Contributor may participate in any such
+    claim at its own expense.
+
+    For example, a Contributor might include the Program in a commercial
+    product offering, Product X. That Contributor is then a Commercial
+    Contributor. If that Commercial Contributor then makes performance claims,
+    or offers warranties related to Product X, those performance claims and
+    warranties are such Commercial Contributor's responsibility alone. Under
+    this section, the Commercial Contributor would have to defend claims
+    against the other Contributors related to those performance claims and
+    warranties, and if a court requires any other Contributor to pay any
+    damages as a result, the Commercial Contributor must pay those damages.
+
+    5. NO WARRANTY
+
+    EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED
+    ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
+    EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR
+    CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A
+    PARTICULAR PURPOSE. Each Recipient is solely responsible for determining
+    the appropriateness of using and distributing the Program and assumes all
+    risks associated with its exercise of rights under this Agreement ,
+    including but not limited to the risks and costs of program errors,
+    compliance with applicable laws, damage to or loss of data, programs or
+    equipment, and unavailability or interruption of operations.
+
+    6. DISCLAIMER OF LIABILITY
+
+    EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR
+    ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
+    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
+    WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
+    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
+    DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+    HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+    7. GENERAL
+
+    If any provision of this Agreement is invalid or unenforceable under
+    applicable law, it shall not affect the validity or enforceability of
+    the remainder of the terms of this Agreement, and without further action
+    by the parties hereto, such provision shall be reformed to the minimum
+    extent necessary to make such provision valid and enforceable.
+
+    If Recipient institutes patent litigation against any entity (including
+    a cross-claim or counterclaim in a lawsuit) alleging that the Program
+    itself (excluding combinations of the Program with other software or
+    hardware) infringes such Recipient's patent(s), then such Recipient's
+    rights granted under Section 2(b) shall terminate as of the date such
+    litigation is filed.
+
+    All Recipient's rights under this Agreement shall terminate if it fails
+    to comply with any of the material terms or conditions of this Agreement
+    and does not cure such failure in a reasonable period of time after
+    becoming aware of such noncompliance. If all Recipient's rights under
+    this Agreement terminate, Recipient agrees to cease use and distribution
+    of the Program as soon as reasonably practicable. However, Recipient's
+    obligations under this Agreement and any licenses granted by Recipient
+    relating to the Program shall continue and survive.
+
+    Everyone is permitted to copy and distribute copies of this Agreement,
+    but in order to avoid inconsistency the Agreement is copyrighted and may
+    only be modified in the following manner. The Agreement Steward reserves
+    the right to publish new versions (including revisions) of this Agreement
+    from time to time. No one other than the Agreement Steward has the right
+    to modify this Agreement. The Eclipse Foundation is the initial Agreement
+    Steward. The Eclipse Foundation may assign the responsibility to serve as
+    the Agreement Steward to a suitable separate entity. Each new version of
+    the Agreement will be given a distinguishing version number. The Program
+    (including Contributions) may always be distributed subject to the version
+    of the Agreement under which it was received. In addition, after a new
+    version of the Agreement is published, Contributor may elect to distribute
+    the Program (including its Contributions) under the new version. Except as
+    expressly stated in Sections 2(a) and 2(b) above, Recipient receives no
+    rights or licenses to the intellectual property of any Contributor under
+    this Agreement, whether expressly, by implication, estoppel or otherwise.
+    All rights in the Program not expressly granted under this Agreement
+    are reserved.
+
+    This Agreement is governed by the laws of the State of New York and the
+    intellectual property laws of the United States of America. No party to
+    this Agreement will bring a legal action under this Agreement more than
+    one year after the cause of action arose. Each party waives its rights to
+    a jury trial in any resulting litigation.
+
+juniversalchardet library (juniversalchardet)
+
+                              MOZILLA PUBLIC LICENSE
+                                    Version 1.1
+
+                                  ---------------
+
+    1. Definitions.
+
+         1.0.1. "Commercial Use" means distribution or otherwise making the
+         Covered Code available to a third party.
+
+         1.1. "Contributor" means each entity that creates or contributes to
+         the creation of Modifications.
+
+         1.2. "Contributor Version" means the combination of the Original
+         Code, prior Modifications used by a Contributor, and the Modifications
+         made by that particular Contributor.
+
+         1.3. "Covered Code" means the Original Code or Modifications or the
+         combination of the Original Code and Modifications, in each case
+         including portions thereof.
+
+         1.4. "Electronic Distribution Mechanism" means a mechanism generally
+         accepted in the software development community for the electronic
+         transfer of data.
+
+         1.5. "Executable" means Covered Code in any form other than Source
+         Code.
+
+         1.6. "Initial Developer" means the individual or entity identified
+         as the Initial Developer in the Source Code notice required by Exhibit
+         A.
+
+         1.7. "Larger Work" means a work which combines Covered Code or
+         portions thereof with code not governed by the terms of this License.
+
+         1.8. "License" means this document.
+
+         1.8.1. "Licensable" means having the right to grant, to the maximum
+         extent possible, whether at the time of the initial grant or
+         subsequently acquired, any and all of the rights conveyed herein.
+
+         1.9. "Modifications" means any addition to or deletion from the
+         substance or structure of either the Original Code or any previous
+         Modifications. When Covered Code is released as a series of files, a
+         Modification is:
+              A. Any addition to or deletion from the contents of a file
+              containing Original Code or previous Modifications.
+
+              B. Any new file that contains any part of the Original Code or
+              previous Modifications.
+
+         1.10. "Original Code" means Source Code of computer software code
+         which is described in the Source Code notice required by Exhibit A as
+         Original Code, and which, at the time of its release under this
+         License is not already Covered Code governed by this License.
+
+         1.10.1. "Patent Claims" means any patent claim(s), now owned or
+         hereafter acquired, including without limitation,  method, process,
+         and apparatus claims, in any patent Licensable by grantor.
+
+         1.11. "Source Code" means the preferred form of the Covered Code for
+         making modifications to it, including all modules it contains, plus
+         any associated interface definition files, scripts used to control
+         compilation and installation of an Executable, or source code
+         differential comparisons against either the Original Code or another
+         well known, available Covered Code of the Contributor's choice. The
+         Source Code can be in a compressed or archival form, provided the
+         appropriate decompression or de-archiving software is widely available
+         for no charge.
+
+         1.12. "You" (or "Your")  means an individual or a legal entity
+         exercising rights under, and complying with all of the terms of, this
+         License or a future version of this License issued under Section 6.1.
+         For legal entities, "You" includes any entity which controls, is
+         controlled by, or is under common control with You. For purposes of
+         this definition, "control" means (a) the power, direct or indirect,
+         to cause the direction or management of such entity, whether by
+         contract or otherwise, or (b) ownership of more than fifty percent
+         (50%) of the outstanding shares or beneficial ownership of such
+         entity.
+
+    2. Source Code License.
+
+         2.1. The Initial Developer Grant.
+         The Initial Developer hereby grants You a world-wide, royalty-free,
+         non-exclusive license, subject to third party intellectual property
+         claims:
+              (a)  under intellectual property rights (other than patent or
+              trademark) Licensable by Initial Developer to use, reproduce,
+              modify, display, perform, sublicense and distribute the Original
+              Code (or portions thereof) with or without Modifications, and/or
+              as part of a Larger Work; and
+
+              (b) under Patents Claims infringed by the making, using or
+              selling of Original Code, to make, have made, use, practice,
+              sell, and offer for sale, and/or otherwise dispose of the
+              Original Code (or portions thereof).
+
+              (c) the licenses granted in this Section 2.1(a) and (b) are
+              effective on the date Initial Developer first distributes
+              Original Code under the terms of this License.
+
+              (d) Notwithstanding Section 2.1(b) above, no patent license is
+              granted: 1) for code that You delete from the Original Code; 2)
+              separate from the Original Code;  or 3) for infringements caused
+              by: i) the modification of the Original Code or ii) the
+              combination of the Original Code with other software or devices.
+
+         2.2. Contributor Grant.
+         Subject to third party intellectual property claims, each Contributor
+         hereby grants You a world-wide, royalty-free, non-exclusive license
+
+              (a)  under intellectual property rights (other than patent or
+              trademark) Licensable by Contributor, to use, reproduce, modify,
+              display, perform, sublicense and distribute the Modifications
+              created by such Contributor (or portions thereof) either on an
+              unmodified basis, with other Modifications, as Covered Code
+              and/or as part of a Larger Work; and
+
+              (b) under Patent Claims infringed by the making, using, or
+              selling of  Modifications made by that Contributor either alone
+              and/or in combination with its Contributor Version (or portions
+              of such combination), to make, use, sell, offer for sale, have
+              made, and/or otherwise dispose of: 1) Modifications made by that
+              Contributor (or portions thereof); and 2) the combination of
+              Modifications made by that Contributor with its Contributor
+              Version (or portions of such combination).
+
+              (c) the licenses granted in Sections 2.2(a) and 2.2(b) are
+              effective on the date Contributor first makes Commercial Use of
+              the Covered Code.
+
+              (d)    Notwithstanding Section 2.2(b) above, no patent license is
+              granted: 1) for any code that Contributor has deleted from the
+              Contributor Version; 2)  separate from the Contributor Version;
+              3)  for infringements caused by: i) third party modifications of
+              Contributor Version or ii)  the combination of Modifications made
+              by that Contributor with other software  (except as part of the
+              Contributor Version) or other devices; or 4) under Patent Claims
+              infringed by Covered Code in the absence of Modifications made by
+              that Contributor.
+
+    3. Distribution Obligations.
+
+         3.1. Application of License.
+         The Modifications which You create or to which You contribute are
+         governed by the terms of this License, including without limitation
+         Section 2.2. The Source Code version of Covered Code may be
+         distributed only under the terms of this License or a future version
+         of this License released under Section 6.1, and You must include a
+         copy of this License with every copy of the Source Code You
+         distribute. You may not offer or impose any terms on any Source Code
+         version that alters or restricts the applicable version of this
+         License or the recipients' rights hereunder. However, You may include
+         an additional document offering the additional rights described in
+         Section 3.5.
+
+         3.2. Availability of Source Code.
+         Any Modification which You create or to which You contribute must be
+         made available in Source Code form under the terms of this License
+         either on the same media as an Executable version or via an accepted
+         Electronic Distribution Mechanism to anyone to whom you made an
+         Executable version available; and if made available via Electronic
+         Distribution Mechanism, must remain available for at least twelve (12)
+         months after the date it initially became available, or at least six
+         (6) months after a subsequent version of that particular Modification
+         has been made available to such recipients. You are responsible for
+         ensuring that the Source Code version remains available even if the
+         Electronic Distribution Mechanism is maintained by a third party.
+
+         3.3. Description of Modifications.
+         You must cause all Covered Code to which You contribute to contain a
+         file documenting the changes You made to create that Covered Code and
+         the date of any change. You must include a prominent statement that
+         the Modification is derived, directly or indirectly, from Original
+         Code provided by the Initial Developer and including the name of the
+         Initial Developer in (a) the Source Code, and (b) in any notice in an
+         Executable version or related documentation in which You describe the
+         origin or ownership of the Covered Code.
+
+         3.4. Intellectual Property Matters
+              (a) Third Party Claims.
+              If Contributor has knowledge that a license under a third party's
+              intellectual property rights is required to exercise the rights
+              granted by such Contributor under Sections 2.1 or 2.2,
+              Contributor must include a text file with the Source Code
+              distribution titled "LEGAL" which describes the claim and the
+              party making the claim in sufficient detail that a recipient will
+              know whom to contact. If Contributor obtains such knowledge after
+              the Modification is made available as described in Section 3.2,
+              Contributor shall promptly modify the LEGAL file in all copies
+              Contributor makes available thereafter and shall take other steps
+              (such as notifying appropriate mailing lists or newsgroups)
+              reasonably calculated to inform those who received the Covered
+              Code that new knowledge has been obtained.
+
+              (b) Contributor APIs.
+              If Contributor's Modifications include an application programming
+              interface and Contributor has knowledge of patent licenses which
+              are reasonably necessary to implement that API, Contributor must
+              also include this information in the LEGAL file.
+
+                   (c)    Representations.
+              Contributor represents that, except as disclosed pursuant to
+              Section 3.4(a) above, Contributor believes that Contributor's
+              Modifications are Contributor's original creation(s) and/or
+              Contributor has sufficient rights to grant the rights conveyed by
+              this License.
+
+         3.5. Required Notices.
+         You must duplicate the notice in Exhibit A in each file of the Source
+         Code.  If it is not possible to put such notice in a particular Source
+         Code file due to its structure, then You must include such notice in a
+         location (such as a relevant directory) where a user would be likely
+         to look for such a notice.  If You created one or more Modification(s)
+         You may add your name as a Contributor to the notice described in
+         Exhibit A.  You must also duplicate this License in any documentation
+         for the Source Code where You describe recipients' rights or ownership
+         rights relating to Covered Code.  You may choose to offer, and to
+         charge a fee for, warranty, support, indemnity or liability
+         obligations to one or more recipients of Covered Code. However, You
+         may do so only on Your own behalf, and not on behalf of the Initial
+         Developer or any Contributor. You must make it absolutely clear than
+         any such warranty, support, indemnity or liability obligation is
+         offered by You alone, and You hereby agree to indemnify the Initial
+         Developer and every Contributor for any liability incurred by the
+         Initial Developer or such Contributor as a result of warranty,
+         support, indemnity or liability terms You offer.
+
+         3.6. Distribution of Executable Versions.
+         You may distribute Covered Code in Executable form only if the
+         requirements of Section 3.1-3.5 have been met for that Covered Code,
+         and if You include a notice stating that the Source Code version of
+         the Covered Code is available under the terms of this License,
+         including a description of how and where You have fulfilled the
+         obligations of Section 3.2. The notice must be conspicuously included
+         in any notice in an Executable version, related documentation or
+         collateral in which You describe recipients' rights relating to the
+         Covered Code. You may distribute the Executable version of Covered
+         Code or ownership rights under a license of Your choice, which may
+         contain terms different from this License, provided that You are in
+         compliance with the terms of this License and that the license for the
+         Executable version does not attempt to limit or alter the recipient's
+         rights in the Source Code version from the rights set forth in this
+         License. If You distribute the Executable version under a different
+         license You must make it absolutely clear that any terms which differ
+         from this License are offered by You alone, not by the Initial
+         Developer or any Contributor. You hereby agree to indemnify the
+         Initial Developer and every Contributor for any liability incurred by
+         the Initial Developer or such Contributor as a result of any such
+         terms You offer.
+
+         3.7. Larger Works.
+         You may create a Larger Work by combining Covered Code with other code
+         not governed by the terms of this License and distribute the Larger
+         Work as a single product. In such a case, You must make sure the
+         requirements of this License are fulfilled for the Covered Code.
+
+    4. Inability to Comply Due to Statute or Regulation.
+
+         If it is impossible for You to comply with any of the terms of this
+         License with respect to some or all of the Covered Code due to
+         statute, judicial order, or regulation then You must: (a) comply with
+         the terms of this License to the maximum extent possible; and (b)
+         describe the limitations and the code they affect. Such description
+         must be included in the LEGAL file described in Section 3.4 and must
+         be included with all distributions of the Source Code. Except to the
+         extent prohibited by statute or regulation, such description must be
+         sufficiently detailed for a recipient of ordinary skill to be able to
+         understand it.
+
+    5. Application of this License.
+
+         This License applies to code to which the Initial Developer has
+         attached the notice in Exhibit A and to related Covered Code.
+
+    6. Versions of the License.
+
+         6.1. New Versions.
+         Netscape Communications Corporation ("Netscape") may publish revised
+         and/or new versions of the License from time to time. Each version
+         will be given a distinguishing version number.
+
+         6.2. Effect of New Versions.
+         Once Covered Code has been published under a particular version of the
+         License, You may always continue to use it under the terms of that
+         version. You may also choose to use such Covered Code under the terms
+         of any subsequent version of the License published by Netscape. No one
+         other than Netscape has the right to modify the terms applicable to
+         Covered Code created under this License.
+
+         6.3. Derivative Works.
+         If You create or use a modified version of this License (which you may
+         only do in order to apply it to code which is not already Covered Code
+         governed by this License), You must (a) rename Your license so that
+         the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape",
+         "MPL", "NPL" or any confusingly similar phrase do not appear in your
+         license (except to note that your license differs from this License)
+         and (b) otherwise make it clear that Your version of the license
+         contains terms which differ from the Mozilla Public License and
+         Netscape Public License. (Filling in the name of the Initial
+         Developer, Original Code or Contributor in the notice described in
+         Exhibit A shall not of themselves be deemed to be modifications of
+         this License.)
+
+    7. DISCLAIMER OF WARRANTY.
+
+         COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
+         WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+         WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF
+         DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
+         THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE
+         IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT,
+         YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE
+         COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER
+         OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
+         ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
+
+    8. TERMINATION.
+
+         8.1.  This License and the rights granted hereunder will terminate
+         automatically if You fail to comply with terms herein and fail to cure
+         such breach within 30 days of becoming aware of the breach. All
+         sublicenses to the Covered Code which are properly granted shall
+         survive any termination of this License. Provisions which, by their
+         nature, must remain in effect beyond the termination of this License
+         shall survive.
+
+         8.2.  If You initiate litigation by asserting a patent infringement
+         claim (excluding declatory judgment actions) against Initial Developer
+         or a Contributor (the Initial Developer or Contributor against whom
+         You file such action is referred to as "Participant")  alleging that:
+
+         (a)  such Participant's Contributor Version directly or indirectly
+         infringes any patent, then any and all rights granted by such
+         Participant to You under Sections 2.1 and/or 2.2 of this License
+         shall, upon 60 days notice from Participant terminate prospectively,
+         unless if within 60 days after receipt of notice You either: (i)
+         agree in writing to pay Participant a mutually agreeable reasonable
+         royalty for Your past and future use of Modifications made by such
+         Participant, or (ii) withdraw Your litigation claim with respect to
+         the Contributor Version against such Participant.  If within 60 days
+         of notice, a reasonable royalty and payment arrangement are not
+         mutually agreed upon in writing by the parties or the litigation claim
+         is not withdrawn, the rights granted by Participant to You under
+         Sections 2.1 and/or 2.2 automatically terminate at the expiration of
+         the 60 day notice period specified above.
+
+         (b)  any software, hardware, or device, other than such Participant's
+         Contributor Version, directly or indirectly infringes any patent, then
+         any rights granted to You by such Participant under Sections 2.1(b)
+         and 2.2(b) are revoked effective as of the date You first made, used,
+         sold, distributed, or had made, Modifications made by that
+         Participant.
+
+         8.3.  If You assert a patent infringement claim against Participant
+         alleging that such Participant's Contributor Version directly or
+         indirectly infringes any patent where such claim is resolved (such as
+         by license or settlement) prior to the initiation of patent
+         infringement litigation, then the reasonable value of the licenses
+         granted by such Participant under Sections 2.1 or 2.2 shall be taken
+         into account in determining the amount or value of any payment or
+         license.
+
+         8.4.  In the event of termination under Sections 8.1 or 8.2 above,
+         all end user license agreements (excluding distributors and resellers)
+         which have been validly granted by You or any distributor hereunder
+         prior to termination shall survive termination.
+
+    9. LIMITATION OF LIABILITY.
+
+         UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+         (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL
+         DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE,
+         OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR
+         ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY
+         CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL,
+         WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
+         COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
+         INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+         LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
+         RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+         PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE
+         EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO
+         THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
+
+    10. U.S. GOVERNMENT END USERS.
+
+         The Covered Code is a "commercial item," as that term is defined in
+         48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer
+         software" and "commercial computer software documentation," as such
+         terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48
+         C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995),
+         all U.S. Government End Users acquire Covered Code with only those
+         rights set forth herein.
+
+    11. MISCELLANEOUS.
+
+         This License represents the complete agreement concerning subject
+         matter hereof. If any provision of this License is held to be
+         unenforceable, such provision shall be reformed only to the extent
+         necessary to make it enforceable. This License shall be governed by
+         California law provisions (except to the extent applicable law, if
+         any, provides otherwise), excluding its conflict-of-law provisions.
+         With respect to disputes in which at least one party is a citizen of,
+         or an entity chartered or registered to do business in the United
+         States of America, any litigation relating to this License shall be
+         subject to the jurisdiction of the Federal Courts of the Northern
+         District of California, with venue lying in Santa Clara County,
+         California, with the losing party responsible for costs, including
+         without limitation, court costs and reasonable attorneys' fees and
+         expenses. The application of the United Nations Convention on
+         Contracts for the International Sale of Goods is expressly excluded.
+         Any law or regulation which provides that the language of a contract
+         shall be construed against the drafter shall not apply to this
+         License.
+
+    12. RESPONSIBILITY FOR CLAIMS.
+
+         As between Initial Developer and the Contributors, each party is
+         responsible for claims and damages arising, directly or indirectly,
+         out of its utilization of rights under this License and You agree to
+         work with Initial Developer and Contributors to distribute such
+         responsibility on an equitable basis. Nothing herein is intended or
+         shall be deemed to constitute any admission of liability.
+
+    13. MULTIPLE-LICENSED CODE.
+
+         Initial Developer may designate portions of the Covered Code as
+         "Multiple-Licensed".  "Multiple-Licensed" means that the Initial
+         Developer permits you to utilize portions of the Covered Code under
+         Your choice of the NPL or the alternative licenses, if any, specified
+         by the Initial Developer in the file described in Exhibit A.
+
+    EXHIBIT A -Mozilla Public License.
+
+         ``The contents of this file are subject to the Mozilla Public License
+         Version 1.1 (the "License"); you may not use this file except in
+         compliance with the License. You may obtain a copy of the License at
+         http://www.mozilla.org/MPL/
+
+         Software distributed under the License is distributed on an "AS IS"
+         basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+         License for the specific language governing rights and limitations
+         under the License.
+
+         The Original Code is ______________________________________.
+
+         The Initial Developer of the Original Code is ________________________.
+         Portions created by ______________________ are Copyright (C) ______
+         _______________________. All Rights Reserved.
+
+         Contributor(s): ______________________________________.
+
+         Alternatively, the contents of this file may be used under the terms
+         of the _____ license (the  "[___] License"), in which case the
+         provisions of [______] License are applicable instead of those
+         above.  If you wish to allow use of your version of this file only
+         under the terms of the [____] License and not to allow others to use
+         your version of this file under the MPL, indicate your decision by
+         deleting  the provisions above and replace  them with the notice and
+         other provisions required by the [___] License.  If you do not delete
+         the provisions above, a recipient may use your version of this file
+         under either the MPL or the [___] License."
+
+         [NOTE: The text of this Exhibit A may differ slightly from the text of
+         the notices in the Source Code files of the Original Code. You should
+         use the text of this Exhibit A rather than the text found in the
+         Original Code Source Code for Your Modifications.]
diff --git a/jackrabbit-webapp/src/main/webapp/WEB-INF/protectedHandlers.properties b/jackrabbit-webapp/src/main/webapp/WEB-INF/protectedHandlers.properties
new file mode 100644
index 0000000..f51e656
--- /dev/null
+++ b/jackrabbit-webapp/src/main/webapp/WEB-INF/protectedHandlers.properties
@@ -0,0 +1,17 @@
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You under the Apache License, Version 2.0
+#  (the "License"); you may not use this file except in compliance with
+#  the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+# ProtectedItemRemoveHandler implementation class
+javax.jcr.tck.access.control.list.handler=org.apache.jackrabbit.server.remoting.davex.AclRemoveHandler
diff --git a/jackrabbit-webapp/src/main/webapp/WEB-INF/web.xml b/jackrabbit-webapp/src/main/webapp/WEB-INF/web.xml
index b8e8741..cb1863b 100644
--- a/jackrabbit-webapp/src/main/webapp/WEB-INF/web.xml
+++ b/jackrabbit-webapp/src/main/webapp/WEB-INF/web.xml
@@ -332,6 +332,11 @@
             <param-value>/WEB-INF/batchread.properties</param-value>
             <description>JcrRemotingServlet: Optional mapping from node type names to default depth.</description>
         </init-param>
+        <init-param>
+          <param-name>protectedhandlers-config</param-name>
+          <param-value>/WEB-INF/protectedHandlers.properties</param-value>
+          <description>JcrRemotingServlet: Handlers for removing protected items.</description>
+        </init-param>
         <!-- init-param>
         	<param-name>concurrency-level</param-name>
         	<param-value>50</param-value>
diff --git a/jackrabbit-webapp/src/main/webapp/bootstrap/missing.jsp b/jackrabbit-webapp/src/main/webapp/bootstrap/missing.jsp
index 7822393..068974e 100644
--- a/jackrabbit-webapp/src/main/webapp/bootstrap/missing.jsp
+++ b/jackrabbit-webapp/src/main/webapp/bootstrap/missing.jsp
@@ -40,6 +40,15 @@ request.setAttribute("title", "Content Repository Setup");
       <input size="40" type="text" name="repository_home" value="jackrabbit">
     </label>
   </p>
+  <p>
+    Repository type:
+    <label>
+      <input type="radio" name="repository_type" value="classic" checked> Classic
+    </label>
+    <label>
+      <input type="radio" name="repository_type" value="oak"> Oak
+    </label>
+  </p>
   <p><input type="submit" value="Create Content Repository"></p>
 </form>
 
diff --git a/jackrabbit-webapp/src/main/webapp/error/classpath.jsp b/jackrabbit-webapp/src/main/webapp/error/classpath.jsp
index 2a4faf3..56a3a70 100644
--- a/jackrabbit-webapp/src/main/webapp/error/classpath.jsp
+++ b/jackrabbit-webapp/src/main/webapp/error/classpath.jsp
@@ -1,29 +1,29 @@
-<%--
-  Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements.  See the NOTICE file distributed with
-  this work for additional information regarding copyright ownership.
-  The ASF licenses this file to You under the Apache License, Version 2.0
-  (the "License"); you may not use this file except in compliance with
-  the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
---%><%
-request.setAttribute("title", "JCR API Not Found");
-%><jsp:include page="../header.jsp"/>
-<p>
-The <code>javax.jcr.Repository</code> interface from the JCR API could not
-be loaded.
-</p>
-<p>
-To resolve this issue, you need to make the <code>jcr-2.0.jar</code> file
-available in the shared classpath of the servlet container. The file is
-available for download from the
-<a href="http://www.jcp.org/en/jsr/summary?id=283">JSR 283</a> web page.
+<%--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+--%><%
+request.setAttribute("title", "JCR API Not Found");
+%><jsp:include page="../header.jsp"/>
+<p>
+The <code>javax.jcr.Repository</code> interface from the JCR API could not
+be loaded.
 </p>
-<jsp:include page="../footer.jsp"/>
+<p>
+To resolve this issue, you need to make the <code>jcr-2.0.jar</code> file
+available in the shared classpath of the servlet container. The file is
+available for download from the
+<a href="http://www.jcp.org/en/jsr/summary?id=283">JSR 283</a> web page.
+</p>
+<jsp:include page="../footer.jsp"/>
diff --git a/jackrabbit-webapp/src/main/webapp/error/repository.jsp b/jackrabbit-webapp/src/main/webapp/error/repository.jsp
index 2d1f713..0af01c4 100644
--- a/jackrabbit-webapp/src/main/webapp/error/repository.jsp
+++ b/jackrabbit-webapp/src/main/webapp/error/repository.jsp
@@ -1,42 +1,42 @@
-<%--
-  Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements.  See the NOTICE file distributed with
-  this work for additional information regarding copyright ownership.
-  The ASF licenses this file to You under the Apache License, Version 2.0
-  (the "License"); you may not use this file except in compliance with
-  the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
---%><%@ page isErrorPage="true"
-             import="org.apache.jackrabbit.util.Text,
-                     java.io.StringWriter,
-                     java.io.PrintWriter"%><%
-request.setAttribute("title", "Repository Error");
-%><jsp:include page="../header.jsp"/>
-<p>
-  The content repository operation failed with the following
-  <%= exception.getClass().getSimpleName() %> error:
-</p>
-<blockquote><%= Text.encodeIllegalXMLCharacters(exception.getMessage()) %></blockquote>
-<p>
-  See the
-  <a href="<%= Text.encodeIllegalXMLCharacters(request.getContextPath()) %>/troubleshooting.jsp">troubleshooting page</a>
-  for ideas on how to resolve this issue.
-</p>
-
-<h2>Exception stack trace</h2>
-<p>
-  Below is the full exception stack trace associated with this error:
-</p>
-<%
-StringWriter buffer = new StringWriter();
-exception.printStackTrace(new PrintWriter(buffer));
-%>
+<%--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+--%><%@ page isErrorPage="true"
+             import="org.apache.jackrabbit.util.Text,
+                     java.io.StringWriter,
+                     java.io.PrintWriter"%><%
+request.setAttribute("title", "Repository Error");
+%><jsp:include page="../header.jsp"/>
+<p>
+  The content repository operation failed with the following
+  <%= exception.getClass().getSimpleName() %> error:
+</p>
+<blockquote><%= Text.encodeIllegalXMLCharacters(exception.getMessage()) %></blockquote>
+<p>
+  See the
+  <a href="<%= Text.encodeIllegalXMLCharacters(request.getContextPath()) %>/troubleshooting.jsp">troubleshooting page</a>
+  for ideas on how to resolve this issue.
+</p>
+
+<h2>Exception stack trace</h2>
+<p>
+  Below is the full exception stack trace associated with this error:
+</p>
+<%
+StringWriter buffer = new StringWriter();
+exception.printStackTrace(new PrintWriter(buffer));
+%>
 <pre><%= Text.encodeIllegalXMLCharacters(buffer.toString()) %></pre>
-<jsp:include page="../footer.jsp"/>
+<jsp:include page="../footer.jsp"/>
diff --git a/jackrabbit-webapp/src/main/webapp/footer.jsp b/jackrabbit-webapp/src/main/webapp/footer.jsp
index 3002785..dc549f3 100644
--- a/jackrabbit-webapp/src/main/webapp/footer.jsp
+++ b/jackrabbit-webapp/src/main/webapp/footer.jsp
@@ -1,26 +1,26 @@
-<%--
-  Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements.  See the NOTICE file distributed with
-  this work for additional information regarding copyright ownership.
-  The ASF licenses this file to You under the Apache License, Version 2.0
-  (the "License"); you may not use this file except in compliance with
-  the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
---%>
-      </div>
-      <div id="footer">
-        <p>
-          Powered by
-          <a href="http://jackrabbit.apache.org/">Apache Jackrabbit</a>.
-        </p>
-      </div>
-    </div>
-  </body>
-</html>
+<%--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+--%>
+      </div>
+      <div id="footer">
+        <p>
+          Powered by
+          <a href="http://jackrabbit.apache.org/">Apache Jackrabbit</a>.
+        </p>
+      </div>
+    </div>
+  </body>
+</html>
diff --git a/jackrabbit-webapp/src/main/webapp/header.jsp b/jackrabbit-webapp/src/main/webapp/header.jsp
index 76db56f..fa4f246 100644
--- a/jackrabbit-webapp/src/main/webapp/header.jsp
+++ b/jackrabbit-webapp/src/main/webapp/header.jsp
@@ -1,89 +1,89 @@
-<%--
-  Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements.  See the NOTICE file distributed with
-  this work for additional information regarding copyright ownership.
-  The ASF licenses this file to You under the Apache License, Version 2.0
-  (the "License"); you may not use this file except in compliance with
-  the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
---%>
-<%@page import="org.apache.jackrabbit.util.Text"%>
-<%
-String title =
-    Text.encodeIllegalXMLCharacters(request.getAttribute("title").toString());
-String context =
-    Text.encodeIllegalXMLCharacters(request.getContextPath());
-%>
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
-          "http://www.w3.org/TR/html4/loose.dtd">
-<html>
-  <head>
-    <title><%= title %></title>
-    <link rel="stylesheet"
-          href="<%= context %>/css/default.css"
-          type="text/css"/>
-    <link rel="shortcut icon"
-          href="<%= context %>/images/favicon.ico"
-          type="image/vnd.microsoft.icon" />
-  </head>
-  <body>
-    <div id="page">
-      <div id="banner">
-        <p id="jcr">
-          <a href="<%= context %>/">
-            <img src="<%= context %>/images/jlogo.gif"
-                 alt="Apache Jackrabbit" height="100" width="336"/>
-          </a>
-        </p>
-        <p id="asf">
-          <a href="http://www.apache.org/">
-            <img src="<%= context %>/images/asf-logo.gif"
-                 alt="Apache Software Foundation" height="100" width="387"/>
-          </a>
-        </p>
-      </div>
-      <div id="navigation">
-        <ul>
-          <li>Jackrabbit JCR Server
-            <ul>
-              <li><a href="<%= context %>/">Welcome</a></li>
-              <li><a href="<%= context %>/webdav-simple.jsp">Standard WebDAV</a></li>
-              <li><a href="<%= context %>/webdav-jcr.jsp">JCR Remoting</a></li>
-              <li><a href="<%= context %>/remote.jsp">Remote access</a></li>
-              <li><a href="<%= context %>/local.jsp">Local access</a></li>
-              <li><a href="<%= context %>/troubleshooting.jsp">Troubleshooting</a></li>
-              <li><a href="<%= context %>/about.jsp">About Jackrabbit</a></li>
-            </ul>
-          </li>
-          <li>Default workspace
-            <ul>
-              <li><a href="<%= context %>/repository/default/">Browse</a></li>
-              <li><a href="<%= context %>/search.jsp">Search</a></li>
-              <li><a href="<%= context %>/populate.jsp">Populate</a></li>
-            </ul>
-          </li>
-          <li>Apache Jackrabbit
-            <ul>
-              <li><a href="http://jackrabbit.apache.org/">Apache Jackrabbit</a></li>
-              <li><a href="http://jackrabbit.apache.org/api/2.3/">Jackrabbit API</a></li>
-              <li><a href="http://wiki.apache.org/jackrabbit/FrontPage">Jackrabbit Wiki</a></li>
-            </ul>
-          </li>
-          <li>JCR
-            <ul>
-              <li><a href="http://jcp.org/en/jsr/detail?id=170">JSR 170</a></li>
-              <li><a href="http://jcp.org/en/jsr/detail?id=283">JSR 283</a></li>
-              <li><a href="http://www.day.com/maven/javax.jcr/javadocs/jcr-2.0/">JCR 2.0 API</a></li>
-            </ul>
-          </li>
-        </ul>
-      </div>
-      <div id="content">
-        <h2><%= title %></h2>
+<%--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+--%>
+<%@page import="org.apache.jackrabbit.util.Text"%>
+<%
+String title =
+    Text.encodeIllegalXMLCharacters(request.getAttribute("title").toString());
+String context =
+    Text.encodeIllegalXMLCharacters(request.getContextPath());
+%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+          "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+  <head>
+    <title><%= title %></title>
+    <link rel="stylesheet"
+          href="<%= context %>/css/default.css"
+          type="text/css"/>
+    <link rel="shortcut icon"
+          href="<%= context %>/images/favicon.ico"
+          type="image/vnd.microsoft.icon" />
+  </head>
+  <body>
+    <div id="page">
+      <div id="banner">
+        <p id="jcr">
+          <a href="<%= context %>/">
+            <img src="<%= context %>/images/jlogo.gif"
+                 alt="Apache Jackrabbit" height="100" width="336"/>
+          </a>
+        </p>
+        <p id="asf">
+          <a href="http://www.apache.org/">
+            <img src="<%= context %>/images/asf-logo.gif"
+                 alt="Apache Software Foundation" height="100" width="387"/>
+          </a>
+        </p>
+      </div>
+      <div id="navigation">
+        <ul>
+          <li>Jackrabbit JCR Server
+            <ul>
+              <li><a href="<%= context %>/">Welcome</a></li>
+              <li><a href="<%= context %>/webdav-simple.jsp">Standard WebDAV</a></li>
+              <li><a href="<%= context %>/webdav-jcr.jsp">JCR Remoting</a></li>
+              <li><a href="<%= context %>/remote.jsp">Remote access</a></li>
+              <li><a href="<%= context %>/local.jsp">Local access</a></li>
+              <li><a href="<%= context %>/troubleshooting.jsp">Troubleshooting</a></li>
+              <li><a href="<%= context %>/about.jsp">About Jackrabbit</a></li>
+            </ul>
+          </li>
+          <li>Default workspace
+            <ul>
+              <li><a href="<%= context %>/repository/default/">Browse</a></li>
+              <li><a href="<%= context %>/search.jsp">Search</a></li>
+              <li><a href="<%= context %>/populate.jsp">Populate</a></li>
+            </ul>
+          </li>
+          <li>Apache Jackrabbit
+            <ul>
+              <li><a href="http://jackrabbit.apache.org/">Apache Jackrabbit</a></li>
+              <li><a href="http://jackrabbit.apache.org/api/2.3/">Jackrabbit API</a></li>
+              <li><a href="http://wiki.apache.org/jackrabbit/FrontPage">Jackrabbit Wiki</a></li>
+            </ul>
+          </li>
+          <li>JCR
+            <ul>
+              <li><a href="http://jcp.org/en/jsr/detail?id=170">JSR 170</a></li>
+              <li><a href="http://jcp.org/en/jsr/detail?id=283">JSR 283</a></li>
+              <li><a href="http://www.day.com/maven/javax.jcr/javadocs/jcr-2.0/">JCR 2.0 API</a></li>
+            </ul>
+          </li>
+        </ul>
+      </div>
+      <div id="content">
+        <h2><%= title %></h2>
diff --git a/jackrabbit-webapp/src/main/webapp/local.jsp b/jackrabbit-webapp/src/main/webapp/local.jsp
index a9782c6..4605e04 100644
--- a/jackrabbit-webapp/src/main/webapp/local.jsp
+++ b/jackrabbit-webapp/src/main/webapp/local.jsp
@@ -1,103 +1,103 @@
-<%--
-  Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements.  See the NOTICE file distributed with
-  this work for additional information regarding copyright ownership.
-  The ASF licenses this file to You under the Apache License, Version 2.0
-  (the "License"); you may not use this file except in compliance with
-  the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
---%><%@page import="org.apache.jackrabbit.util.Text"%><%
-request.setAttribute("title", "Local Repository Access");
-%><jsp:include page="header.jsp"/>
-<p>
-  The content repository within this web application can be accessed
-  locally by other web applications within the same servlet container.
-  Local access is much faster than <a href="remote.jsp">remote access</a>.
-</p>
-<p>
-  The content repository is made available both through JNDI and the
-  web application context.
-</p>
-
-<h3>Accessing the repository through JNDI</h3>
-<p>
-  By default the repository is only made available in a dummy JNDI directory
-  local to this web application. However, you can make the repository globally
-  available if your servlet container allows a web application to modify the
-  global JNDI directory or you are using some other JNDI directory that can
-  manage unserializable Java objects.
-</p>
-<p>
-  To bind the the repository to such a JNDI directory, you need to modify
-  the <code>java.naming</code> parameters in either the /WEB-INF/web.xml
-  deployment descriptor or the jackrabbit/bootstrap.properties file. You need
-  to redeploy this web application to activate the changes.
-</p>
-<p>
-  Use the following code to access a repository bound in a JNDI directory:
-</p>
-<pre>
-<b>import</b> javax.jcr.Repository;
-<b>import</b> javax.naming.Context;
-<b>import</b> javax.naming.InitialContext;
-
-Context context = <b>new</b> InitialContext(...);
-Repository repository = (Repository) context.lookup(...);
-</pre>
-
-<h3>Accessing the repository through servlet context</h3>
-<p>
-  This web application makes the repository available as the
-  <code>javax.jcr.Repository</code> attribute in the application context.
-  If your servlet container supports cross-context access, you can
-  access the repository directly using that attribute.
-</p>
-<p>
-  For example in <a href="http://tomcat.apache.org/">Apache Tomcat</a>
-  you can enable cross-context access by setting the <code>crossContext</code>
-  attribute to true in the <Context/> configuration.
-</p>
-<p>
-  Use the following code to access a repository through the servlet context:
-</p>
-<pre>
-<b>import</b> javax.jcr.Repository;
-<b>import</b> javax.servlet.ServletContext;
-
-ServletContext context = ...; // <em>context of your servlet</em>
-ServletContext jackrabbit =
-    context.getContext("<em><%= Text.encodeIllegalXMLCharacters(request.getContextPath()) %></em>");
-Repository repository = (Repository)
-    context.getAttribute(Repository.<b>class</b>.getName()).
-</pre>
-
-<h3>Using the jackrabbit-jcr-servlet component</h3>
-<p>
-  The <em>jackrabbit-jcr-servlet</em> component contains utility classes
-  for use within JCR web applications. With that component you can hide
-  both the above and the <a href="remote.jsp">remote access</a> options
-  from your code, and use just the following to access a repository:
-</p>
-<pre>
-<b>import</b> javax.jcr.Repository;
-<b>import</b> org.apache.jackrabbit.servlet.ServletRepository;
-
-<b>public class</b> MyServlet <b>extends</b> HttpServlet {
-
-    <b>private final</b> Repository repository = <b>new</b> ServletRepository(<b>this</b>);
-
-    // ...
-
-}
-</pre>
-<p>
-  See the jackrabbit-jcr-servlet documentation for more details.
-</p>
-<jsp:include page="footer.jsp"/>
+<%--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+--%><%@page import="org.apache.jackrabbit.util.Text"%><%
+request.setAttribute("title", "Local Repository Access");
+%><jsp:include page="header.jsp"/>
+<p>
+  The content repository within this web application can be accessed
+  locally by other web applications within the same servlet container.
+  Local access is much faster than <a href="remote.jsp">remote access</a>.
+</p>
+<p>
+  The content repository is made available both through JNDI and the
+  web application context.
+</p>
+
+<h3>Accessing the repository through JNDI</h3>
+<p>
+  By default the repository is only made available in a dummy JNDI directory
+  local to this web application. However, you can make the repository globally
+  available if your servlet container allows a web application to modify the
+  global JNDI directory or you are using some other JNDI directory that can
+  manage unserializable Java objects.
+</p>
+<p>
+  To bind the the repository to such a JNDI directory, you need to modify
+  the <code>java.naming</code> parameters in either the /WEB-INF/web.xml
+  deployment descriptor or the jackrabbit/bootstrap.properties file. You need
+  to redeploy this web application to activate the changes.
+</p>
+<p>
+  Use the following code to access a repository bound in a JNDI directory:
+</p>
+<pre>
+<b>import</b> javax.jcr.Repository;
+<b>import</b> javax.naming.Context;
+<b>import</b> javax.naming.InitialContext;
+
+Context context = <b>new</b> InitialContext(...);
+Repository repository = (Repository) context.lookup(...);
+</pre>
+
+<h3>Accessing the repository through servlet context</h3>
+<p>
+  This web application makes the repository available as the
+  <code>javax.jcr.Repository</code> attribute in the application context.
+  If your servlet container supports cross-context access, you can
+  access the repository directly using that attribute.
+</p>
+<p>
+  For example in <a href="http://tomcat.apache.org/">Apache Tomcat</a>
+  you can enable cross-context access by setting the <code>crossContext</code>
+  attribute to true in the <Context/> configuration.
+</p>
+<p>
+  Use the following code to access a repository through the servlet context:
+</p>
+<pre>
+<b>import</b> javax.jcr.Repository;
+<b>import</b> javax.servlet.ServletContext;
+
+ServletContext context = ...; // <em>context of your servlet</em>
+ServletContext jackrabbit =
+    context.getContext("<em><%= Text.encodeIllegalXMLCharacters(request.getContextPath()) %></em>");
+Repository repository = (Repository)
+    context.getAttribute(Repository.<b>class</b>.getName()).
+</pre>
+
+<h3>Using the jackrabbit-jcr-servlet component</h3>
+<p>
+  The <em>jackrabbit-jcr-servlet</em> component contains utility classes
+  for use within JCR web applications. With that component you can hide
+  both the above and the <a href="remote.jsp">remote access</a> options
+  from your code, and use just the following to access a repository:
+</p>
+<pre>
+<b>import</b> javax.jcr.Repository;
+<b>import</b> org.apache.jackrabbit.servlet.ServletRepository;
+
+<b>public class</b> MyServlet <b>extends</b> HttpServlet {
+
+    <b>private final</b> Repository repository = <b>new</b> ServletRepository(<b>this</b>);
+
+    // ...
+
+}
+</pre>
+<p>
+  See the jackrabbit-jcr-servlet documentation for more details.
+</p>
+<jsp:include page="footer.jsp"/>
diff --git a/jackrabbit-webapp/src/main/webapp/populate.jsp b/jackrabbit-webapp/src/main/webapp/populate.jsp
index 02675e5..4dd45ca 100644
--- a/jackrabbit-webapp/src/main/webapp/populate.jsp
+++ b/jackrabbit-webapp/src/main/webapp/populate.jsp
@@ -31,19 +31,18 @@
                      javax.jcr.Repository,
                      javax.jcr.Session,
                      javax.jcr.SimpleCredentials,
-                     javax.swing.text.AttributeSet,
-                     javax.swing.text.html.HTML,
-                     javax.swing.text.html.HTMLDocument,
-                     javax.swing.text.html.HTMLEditorKit,
                      org.apache.jackrabbit.j2ee.RepositoryAccessServlet,
-                     org.apache.jackrabbit.util.Text"
+                     org.apache.jackrabbit.util.Text,
+                     org.json.simple.JSONArray,
+                     org.json.simple.JSONObject,
+                     org.json.simple.JSONValue"
  %><%@ page contentType="text/html;charset=UTF-8" %><%
     Repository rep;
     Session jcrSession;
     String wspName;
     try {
         rep = RepositoryAccessServlet.getRepository(pageContext.getServletContext());
-        jcrSession = rep.login(new SimpleCredentials("user", "".toCharArray()));
+        jcrSession = rep.login(new SimpleCredentials("admin", "admin".toCharArray()));
         wspName = jcrSession.getWorkspace().getName();
     } catch (Throwable e) {
         %>Error while accessing the repository: <font color="red"><%= Text.encodeIllegalXMLCharacters(e.getMessage()) %></font><br><%
@@ -280,6 +279,8 @@ request.setAttribute("title", "Populate workspace " + wspName);
 
     public static class Search {
 
+        private static final String CHARSET = "UTF-8";
+
         private final String filetype;
 
         private final String term;
@@ -293,36 +294,38 @@ request.setAttribute("title", "Populate workspace " + wspName);
         }
 
         public URL[] getURLs() throws Exception {
-            List urls = new ArrayList();
-            String query = term + " filetype:" + filetype;
-            URL google = new URL("http://www.google.com/search?q=" +
-                    URLEncoder.encode(query, "UTF-8") + "&start=" + start);
-            URLConnection con = google.openConnection();
-            con.setRequestProperty("User-Agent", "");
-            InputStream in = con.getInputStream();
+            List<URL> urls = new ArrayList<URL>();
+            String googleBaseUrl = "http://ajax.googleapis.com/ajax/services/search/web";
+            String googleQueryString = "?v=1.0&start=" + start + "&rsz=8&q=";
+            String query = term + " filetype:" + this.filetype;
+            URL google = new URL(googleBaseUrl + googleQueryString + URLEncoder.encode(query, CHARSET));
+            InputStreamReader reader = null;
             try {
-                HTMLEditorKit kit = new HTMLEditorKit();
-                HTMLDocument doc = new HTMLDocument();
-                doc.putProperty("IgnoreCharsetDirective", Boolean.TRUE);
-                kit.read(new InputStreamReader(in, "UTF-8"), doc, 0);
-                HTMLDocument.Iterator it = doc.getIterator(HTML.Tag.A);
-                while (it.isValid()) {
-                    AttributeSet attr = it.getAttributes();
-                    if (attr != null) {
-                        String href = (String) attr.getAttribute(HTML.Attribute.HREF);
-                        if (href != null && href.endsWith("." + filetype)) {
-                            URL url = new URL(new URL("http", "www.google.com", "dummy"), href);
-                            if (url.getHost().indexOf("google") == -1) {
-                                urls.add(url);
+                URLConnection conn = google.openConnection();
+                conn.setRequestProperty("Referer", "http://jackrabbit.apache.org/");
+                reader = new InputStreamReader(conn.getInputStream(), CHARSET);
+                Object obj = JSONValue.parse(reader);
+                JSONObject jsonObject = (JSONObject) obj;
+                JSONObject responseData = (JSONObject) jsonObject.get("responseData");
+                if (responseData != null) {
+                    JSONArray results = (JSONArray) responseData.get("results");
+                    if (results != null) {
+                        for (int i = 0; i < results.size(); i++) {
+                            JSONObject result = (JSONObject) results.get(i);
+                            if (result != null) {
+                                String urlString = (String) result.get("url");
+                                URL url = new URL(new URL("http", "www.google.com", "dummy"), urlString);
+                                if (!url.getHost().contains("google")) {
+                                    urls.add(url);
+                                }
                             }
                         }
                     }
-                    it.next();
                 }
             } finally {
-                in.close();
+                reader.close();
             }
-            return (URL[]) urls.toArray(new URL[urls.size()]);
+            return urls.toArray(new URL[urls.size()]);
         }
     }
 
diff --git a/jackrabbit-webapp/src/main/webapp/remote.jsp b/jackrabbit-webapp/src/main/webapp/remote.jsp
index 2613d48..1eedcfb 100644
--- a/jackrabbit-webapp/src/main/webapp/remote.jsp
+++ b/jackrabbit-webapp/src/main/webapp/remote.jsp
@@ -1,106 +1,106 @@
-<%@ page import="java.net.URI"%><%--
-  Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements.  See the NOTICE file distributed with
-  this work for additional information regarding copyright ownership.
-  The ASF licenses this file to You under the Apache License, Version 2.0
-  (the "License"); you may not use this file except in compliance with
-  the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
---%><%@page import="org.apache.jackrabbit.util.Text"%><%
-request.setAttribute("title", "Remote Repository Access");
-
-URI uri = new URI(request.getRequestURL().toString());
-String base =
-    uri.getScheme() + "://" + uri.getHost() + ":" + uri.getPort()
-    + request.getContextPath();
-base = Text.encodeIllegalXMLCharacters(base);
-%><jsp:include page="header.jsp"/>
-<p>
-  The content repository within this web application is made available
-  to remote clients through
-  <a href="http://java.sun.com/javase/technologies/core/basic/rmi/">RMI</a>
-  and the <em>jackrabbit-jcr-rmi</em> component.
-<p>
-<p>
-  The remote repository stub is available both in the RMI registry
-  (one is started automatically by this web application if not already running)
-  and as a direct HTTP download. The default URLs for accessing the remote
-  repository are:
-</p>
-<ul>
-  <li>RMI registry: //localhost/jackrabbit.repository</li>
-  <li>HTTP download: <%= base %>/rmi</li>
-</ul>
-<p>
-  Note that the above URLs are the defaults. You can disable or change them
-  by modifying the /WEB-INF/web.xml deployment descriptor.
-</p>
-
-<h3>Accessing the remote repository</h3>
-<p>
-  To access the remote content repository you need to use the
-  <em>jackrabbit-jcr-rmi</em> component in your application. If you use
-  Maven 2, you can declare the JCR and jackrabbit-jcr-rmi dependencies
-  like this:
-</p>
-<pre><dependency>
-  <groupId>javax.jcr</groupId>
-  <artifactId>jcr</artifactId>
-  <version>1.0</version>
-</dependency>
-<dependency>
-  <groupId>org.apache.jackrabbit</groupId>
-  <artifactId>jackrabbit-jcr-rmi</artifactId>
-  <version>1.4</version>
-</dependency>
-</pre>
-<p>
-  With that dependency in place, you can use either the RMI registry or
-  the direct HTTP download to access the repository.
-</p>
-<p>
-  The required code for accessing the repository using the RMI registry is:
-</p>
-<pre>
-<b>import</b> javax.jcr.Repository;
-<b>import</b> org.apache.jackrabbit.rmi.repository.RMIRemoteRepository;
-
-Repository repository =
-    <b>new</b> RMIRemoteRepository("<em>//localhost/jackrabbit.repository</em>");
-</pre>
-<p>
-  The required code for accessing the repository using the RMI registry is:
-</p>
-<pre>
-<b>import</b> javax.jcr.Repository;
-<b>import</b> org.apache.jackrabbit.rmi.repository.URLRemoteRepository;
-
-Repository repository =
-    <b>new</b> URLRemoteRepository("<em><%= base %>/rmi</em>");
-</pre>
-<p>
-  See the <a href="http://jcp.org/en/jsr/detail?id=170">JCR specification</a>
-  and the
-  <a href="http://www.day.com/maven/jsr170/javadocs/jcr-1.0/javax/jcr/Repository.html">Repository</a>
-  javadoc for details on what to do with the acquired Repository instance.
-</p>
-
-<h3>Remote access performance</h3>
-<p>
-  Note that the design goal of the current jackrabbit-jcr-rmi component
-  is correct and complete functionality instead of performance, so you should
-  not rely on remote access for performance-critical applications.
-</p>
-<p>
-  You may want to look at the Jackrabbit clustering feature for best
-  performance for concurrently accessing the repository on multiple separate
-  servers.
-</p>
-<jsp:include page="footer.jsp"/>
+<%@ page import="java.net.URI"%><%--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+--%><%@page import="org.apache.jackrabbit.util.Text"%><%
+request.setAttribute("title", "Remote Repository Access");
+
+URI uri = new URI(request.getRequestURL().toString());
+String base =
+    uri.getScheme() + "://" + uri.getHost() + ":" + uri.getPort()
+    + request.getContextPath();
+base = Text.encodeIllegalXMLCharacters(base);
+%><jsp:include page="header.jsp"/>
+<p>
+  The content repository within this web application is made available
+  to remote clients through
+  <a href="http://java.sun.com/javase/technologies/core/basic/rmi/">RMI</a>
+  and the <em>jackrabbit-jcr-rmi</em> component.
+<p>
+<p>
+  The remote repository stub is available both in the RMI registry
+  (one is started automatically by this web application if not already running)
+  and as a direct HTTP download. The default URLs for accessing the remote
+  repository are:
+</p>
+<ul>
+  <li>RMI registry: //localhost/jackrabbit.repository</li>
+  <li>HTTP download: <%= base %>/rmi</li>
+</ul>
+<p>
+  Note that the above URLs are the defaults. You can disable or change them
+  by modifying the /WEB-INF/web.xml deployment descriptor.
+</p>
+
+<h3>Accessing the remote repository</h3>
+<p>
+  To access the remote content repository you need to use the
+  <em>jackrabbit-jcr-rmi</em> component in your application. If you use
+  Maven 2, you can declare the JCR and jackrabbit-jcr-rmi dependencies
+  like this:
+</p>
+<pre><dependency>
+  <groupId>javax.jcr</groupId>
+  <artifactId>jcr</artifactId>
+  <version>1.0</version>
+</dependency>
+<dependency>
+  <groupId>org.apache.jackrabbit</groupId>
+  <artifactId>jackrabbit-jcr-rmi</artifactId>
+  <version>1.4</version>
+</dependency>
+</pre>
+<p>
+  With that dependency in place, you can use either the RMI registry or
+  the direct HTTP download to access the repository.
+</p>
+<p>
+  The required code for accessing the repository using the RMI registry is:
+</p>
+<pre>
+<b>import</b> javax.jcr.Repository;
+<b>import</b> org.apache.jackrabbit.rmi.repository.RMIRemoteRepository;
+
+Repository repository =
+    <b>new</b> RMIRemoteRepository("<em>//localhost/jackrabbit.repository</em>");
+</pre>
+<p>
+  The required code for accessing the repository using the RMI registry is:
+</p>
+<pre>
+<b>import</b> javax.jcr.Repository;
+<b>import</b> org.apache.jackrabbit.rmi.repository.URLRemoteRepository;
+
+Repository repository =
+    <b>new</b> URLRemoteRepository("<em><%= base %>/rmi</em>");
+</pre>
+<p>
+  See the <a href="http://jcp.org/en/jsr/detail?id=170">JCR specification</a>
+  and the
+  <a href="http://www.day.com/maven/jsr170/javadocs/jcr-1.0/javax/jcr/Repository.html">Repository</a>
+  javadoc for details on what to do with the acquired Repository instance.
+</p>
+
+<h3>Remote access performance</h3>
+<p>
+  Note that the design goal of the current jackrabbit-jcr-rmi component
+  is correct and complete functionality instead of performance, so you should
+  not rely on remote access for performance-critical applications.
+</p>
+<p>
+  You may want to look at the Jackrabbit clustering feature for best
+  performance for concurrently accessing the repository on multiple separate
+  servers.
+</p>
+<jsp:include page="footer.jsp"/>
diff --git a/jackrabbit-webapp/src/main/webapp/troubleshooting.jsp b/jackrabbit-webapp/src/main/webapp/troubleshooting.jsp
index aa1be52..4aec1f2 100644
--- a/jackrabbit-webapp/src/main/webapp/troubleshooting.jsp
+++ b/jackrabbit-webapp/src/main/webapp/troubleshooting.jsp
@@ -1,105 +1,105 @@
-<%--
-  Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements.  See the NOTICE file distributed with
-  this work for additional information regarding copyright ownership.
-  The ASF licenses this file to You under the Apache License, Version 2.0
-  (the "License"); you may not use this file except in compliance with
-  the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
---%><%@page import="org.apache.jackrabbit.util.Text,
-                    java.io.StringWriter,
-                    java.io.PrintWriter"%><%
-request.setAttribute("title", "Troubleshooting");
-%><jsp:include page="header.jsp"/>
-<p>
-  If you experience problems with the Jackrabbit JCR server, please
-  check the following:
-</p>
-<ol>
-  <li>
-    Did you encounter an exception? Copy the exception stack trace somewhere
-    so you don't loose it. The stack trace contains valuable information
-    for the Jackrabbit developers if you need to file a bug report for the
-    problem you encountered.
-  </li>
-  <li>
-    Is the repository up and running? Try browsing the
-    <a href="<%= Text.encodeIllegalXMLCharacters(request.getContextPath()) %>/repository/default/">default workspace</a>
-    to check if you can still see any content in the repository. You will
-    see an error message if the repository is not available.
-  </li>
-  <li>
-    What were you trying to do? Try to verify that your client code or
-    other manner of repository use is correct. Did it work before or are
-    you trying to do something new?
-  </li>
-  <li>
-    Are there any notable log entries? Check the log files for any related
-    warnings or errors. By default the Jackrabbit JCR Server writes log
-    entries to the standard output of the servlet container. You can customize
-    logging by editing the <code>/WEB-INF/log4j.xml</code> file and
-    redeploying this web application.
-  </li>
-</ol>
-<p>
-  If none of the above steps help you identify or resolve the problem,
-  you can contact the Jackrabbit users mailing list or report the problem
-  in the Jackrabbit issue tracker to get support from the Jackrabbit community.
-  When contacting the community, please include any relevant details related
-  to the above questions and the environment information shown at the end
-  of this page.
-</p>
-
-<h2>Jackrabbit mailing list</h2>
-<p>
-  The Jackrabbit user mailing list, users at jackrabbit.apache.org, is the
-  place to discuss any problems or other issues regarding the use of
-  Apache Jackrabbit (or JCR content repositories in general).
-</p>
-<p>
-  Feel free to subscribe the mailing list or browse the archives listed as
-  described in the
-  <a href="http://jackrabbit.apache.org/mail-lists.html">Jackrabbit mailing lists</a>
-  page.
-</p>
-
-<h2>Jackrabbit issue tracker</h2>
-<p>
-  If you think you've identified a defect in Jackrabbit, you're welcome
-  to file a bug report in the
-  <a href="https://issues.apache.org/jira/browse/JCR">Jackrabbit issue tracker</a>.
-  You can also use the issue tracker to request new features and other
-  improvements.
-</p>
-<p>
-  You need an account in the issue tracker to report new issues or to comment
-  on existing. Use the
-  <a href="https://issues.apache.org/jira/secure/Signup!default.jspa">registration form</a>
-  if you don't already have an account. No account is needed browsing
-  and searching existing issues.
-</p>
-
-<h2>Environment information</h2>
-<p>
-  This instance of the Jackrabbit JCR Server is running in
-  a <em><%= Text.encodeIllegalXMLCharacters(application.getServerInfo()) %></em> servlet container
-  that supports the Java Servlet API version
-  <%= application.getMajorVersion() %>.<%= application.getMinorVersion() %>.
-</p>
-<p>
-  Details of the Java and operating system environment are included in
-  the system properties shown below:
-</p>
-<%
-StringWriter buffer = new StringWriter();
-System.getProperties().list(new PrintWriter(buffer));
-%>
-<pre><%= Text.encodeIllegalXMLCharacters(buffer.toString()) %></pre>
-<jsp:include page="footer.jsp"/>
+<%--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+--%><%@page import="org.apache.jackrabbit.util.Text,
+                    java.io.StringWriter,
+                    java.io.PrintWriter"%><%
+request.setAttribute("title", "Troubleshooting");
+%><jsp:include page="header.jsp"/>
+<p>
+  If you experience problems with the Jackrabbit JCR server, please
+  check the following:
+</p>
+<ol>
+  <li>
+    Did you encounter an exception? Copy the exception stack trace somewhere
+    so you don't loose it. The stack trace contains valuable information
+    for the Jackrabbit developers if you need to file a bug report for the
+    problem you encountered.
+  </li>
+  <li>
+    Is the repository up and running? Try browsing the
+    <a href="<%= Text.encodeIllegalXMLCharacters(request.getContextPath()) %>/repository/default/">default workspace</a>
+    to check if you can still see any content in the repository. You will
+    see an error message if the repository is not available.
+  </li>
+  <li>
+    What were you trying to do? Try to verify that your client code or
+    other manner of repository use is correct. Did it work before or are
+    you trying to do something new?
+  </li>
+  <li>
+    Are there any notable log entries? Check the log files for any related
+    warnings or errors. By default the Jackrabbit JCR Server writes log
+    entries to the standard output of the servlet container. You can customize
+    logging by editing the <code>/WEB-INF/log4j.xml</code> file and
+    redeploying this web application.
+  </li>
+</ol>
+<p>
+  If none of the above steps help you identify or resolve the problem,
+  you can contact the Jackrabbit users mailing list or report the problem
+  in the Jackrabbit issue tracker to get support from the Jackrabbit community.
+  When contacting the community, please include any relevant details related
+  to the above questions and the environment information shown at the end
+  of this page.
+</p>
+
+<h2>Jackrabbit mailing list</h2>
+<p>
+  The Jackrabbit user mailing list, users at jackrabbit.apache.org, is the
+  place to discuss any problems or other issues regarding the use of
+  Apache Jackrabbit (or JCR content repositories in general).
+</p>
+<p>
+  Feel free to subscribe the mailing list or browse the archives listed as
+  described in the
+  <a href="http://jackrabbit.apache.org/mail-lists.html">Jackrabbit mailing lists</a>
+  page.
+</p>
+
+<h2>Jackrabbit issue tracker</h2>
+<p>
+  If you think you've identified a defect in Jackrabbit, you're welcome
+  to file a bug report in the
+  <a href="https://issues.apache.org/jira/browse/JCR">Jackrabbit issue tracker</a>.
+  You can also use the issue tracker to request new features and other
+  improvements.
+</p>
+<p>
+  You need an account in the issue tracker to report new issues or to comment
+  on existing. Use the
+  <a href="https://issues.apache.org/jira/secure/Signup!default.jspa">registration form</a>
+  if you don't already have an account. No account is needed browsing
+  and searching existing issues.
+</p>
+
+<h2>Environment information</h2>
+<p>
+  This instance of the Jackrabbit JCR Server is running in
+  a <em><%= Text.encodeIllegalXMLCharacters(application.getServerInfo()) %></em> servlet container
+  that supports the Java Servlet API version
+  <%= application.getMajorVersion() %>.<%= application.getMinorVersion() %>.
+</p>
+<p>
+  Details of the Java and operating system environment are included in
+  the system properties shown below:
+</p>
+<%
+StringWriter buffer = new StringWriter();
+System.getProperties().list(new PrintWriter(buffer));
+%>
+<pre><%= Text.encodeIllegalXMLCharacters(buffer.toString()) %></pre>
+<jsp:include page="footer.jsp"/>
diff --git a/jackrabbit-webapp/src/main/webapp/webdav-jcr.jsp b/jackrabbit-webapp/src/main/webapp/webdav-jcr.jsp
index 09cdf21..0222615 100644
--- a/jackrabbit-webapp/src/main/webapp/webdav-jcr.jsp
+++ b/jackrabbit-webapp/src/main/webapp/webdav-jcr.jsp
@@ -1,87 +1,88 @@
-<%@ page import="org.apache.jackrabbit.j2ee.JCRWebdavServerServlet,
-                 java.net.URI"
-%><%--
-  Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements.  See the NOTICE file distributed with
-  this work for additional information regarding copyright ownership.
-  The ASF licenses this file to You under the Apache License, Version 2.0
-  (the "License"); you may not use this file except in compliance with
-  the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
---%><%@page import="org.apache.jackrabbit.util.Text"%><%
-request.setAttribute("title", "JCR Remoting Server");
-
-URI uri = new URI(request.getRequestURL().toString());
-String href =
-    uri.getScheme() + "://" + uri.getHost() + ":" + uri.getPort()
-    + request.getContextPath()
-    + JCRWebdavServerServlet.getPathPrefix(pageContext.getServletContext());
-href = Text.encodeIllegalXMLCharacters(href);
-String shref = href + "/default/jcr:root";
-%><jsp:include page="header.jsp"/>
-<p>
-  The JCR Remoting Server provides an item-based WebDAV view to the
-  JCR repository, mapping the functionality provided by JSR 170 to the
-  WebDAV protocol in order to allow remote content repository access
-  via WebDAV.
-</p>
-<p>
-  See the draft document
-  <a href="http://jackrabbit.apache.org/JCR_Webdav_Protocol.doc">JCR_Webdav_Protocol.zip</a>
-  for more details regarding this remoting protocol.
-</p>
-<p>
-  Batch read and write as well as the missing functionality (cross workspace
-  copy and clone) has been addressed with a <a href="webdav-remoting.jsp">extension</a>
-  to the remoting server.
-</p>
-
-<h3>Access the content repository</h3>
-<p>
-  Use the following URLs to access the content repository in your remoting client:
-</p>
-<dl>
-<dt><a href="<%= href %>"><%= href %></a></dt>
-<dd>to access all workspaces of your JCR repository</dd>
-<dt><a href="<%= shref %>"><%= shref %></a></dt>
-<dd>to access a single workspace (example with workspace named 'default')</dd>
-</dl>
-
-<h3>Supported WebDAV functionality</h3>
-<p>
-  This implementation focuses on replicating all JCR features for remote
-  access instead of providing standard WebDAV functionality or compatibility
-  with existing WebDAV clients.
-</p>
-<p>
-  The following RFCs are used to implement the remoting functionality:
-</p>
-<ul>
-  <li><a href="http://www.ietf.org/rfc/rfc2518.txt">RFC 2518</a> (WebDAV 1,2)</li>
-  <li><a href="http://www.ietf.org/rfc/rfc3253.txt">RFC 3253</a> (DeltaV)</li>
-  <li><a href="http://www.ietf.org/rfc/rfc3648.txt">RFC 3648</a> (Ordering)</li>
-  <li><a href="http://greenbytes.de/tech/webdav/draft-reschke-webdav-search-latest.html">Internet Draft WebDAV Search</a></li>
-</ul>
-
-<h3>JCR Remoting Client</h3>
-<p>
-  For the client counterpart of this WebDAV servlet please take a look at the
-  <a href="http://svn.apache.org/repos/asf/jackrabbit/trunk/jackrabbit-spi2dav">Jackrabbit SPI2DAV</a>
-  module.
-</p>
-
-<h3>Configuration</h3>
-<ul>
-  <li>Context Path: <%= Text.encodeIllegalXMLCharacters(request.getContextPath()) %></li>
-  <li>Resource Path Prefix: <%= Text.encodeIllegalXMLCharacters(JCRWebdavServerServlet.getPathPrefix(pageContext.getServletContext())) %></li>
-  <li>Workspace Name: <i>optional</i> (available workspaces are mapped as resources)</li>
-  <li>Additional servlet configuration: see <i>/WEB-INF/web.xml</i></li>
-</ul>
-<jsp:include page="footer.jsp"/>
+<%@ page import="org.apache.jackrabbit.j2ee.JCRWebdavServerServlet,
+                 java.net.URI"
+%><%--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+--%><%@page import="org.apache.jackrabbit.util.Text"%><%
+request.setAttribute("title", "JCR Remoting Server");
+
+URI uri = new URI(request.getRequestURL().toString());
+int port = uri.getPort();
+String href =
+    uri.getScheme() + "://" + uri.getHost() + (port == -1 ? "" : (":" + port))
+    + request.getContextPath()
+    + JCRWebdavServerServlet.getPathPrefix(pageContext.getServletContext());
+href = Text.encodeIllegalXMLCharacters(href);
+String shref = href + "/default/jcr:root";
+%><jsp:include page="header.jsp"/>
+<p>
+  The JCR Remoting Server provides an item-based WebDAV view to the
+  JCR repository, mapping the functionality provided by JSR 170 to the
+  WebDAV protocol in order to allow remote content repository access
+  via WebDAV.
+</p>
+<p>
+  See the draft document
+  <a href="http://jackrabbit.apache.org/JCR_Webdav_Protocol.doc">JCR_Webdav_Protocol.zip</a>
+  for more details regarding this remoting protocol.
+</p>
+<p>
+  Batch read and write as well as the missing functionality (cross workspace
+  copy and clone) has been addressed with a <a href="webdav-remoting.jsp">extension</a>
+  to the remoting server.
+</p>
+
+<h3>Access the content repository</h3>
+<p>
+  Use the following URLs to access the content repository in your remoting client:
+</p>
+<dl>
+<dt><a href="<%= href %>"><%= href %></a></dt>
+<dd>to access all workspaces of your JCR repository</dd>
+<dt><a href="<%= shref %>"><%= shref %></a></dt>
+<dd>to access a single workspace (example with workspace named 'default')</dd>
+</dl>
+
+<h3>Supported WebDAV functionality</h3>
+<p>
+  This implementation focuses on replicating all JCR features for remote
+  access instead of providing standard WebDAV functionality or compatibility
+  with existing WebDAV clients.
+</p>
+<p>
+  The following RFCs are used to implement the remoting functionality:
+</p>
+<ul>
+  <li><a href="http://www.ietf.org/rfc/rfc2518.txt">RFC 2518</a> (WebDAV 1,2)</li>
+  <li><a href="http://www.ietf.org/rfc/rfc3253.txt">RFC 3253</a> (DeltaV)</li>
+  <li><a href="http://www.ietf.org/rfc/rfc3648.txt">RFC 3648</a> (Ordering)</li>
+  <li><a href="http://greenbytes.de/tech/webdav/draft-reschke-webdav-search-latest.html">Internet Draft WebDAV Search</a></li>
+</ul>
+
+<h3>JCR Remoting Client</h3>
+<p>
+  For the client counterpart of this WebDAV servlet please take a look at the
+  <a href="http://svn.apache.org/repos/asf/jackrabbit/trunk/jackrabbit-spi2dav">Jackrabbit SPI2DAV</a>
+  module.
+</p>
+
+<h3>Configuration</h3>
+<ul>
+  <li>Context Path: <%= Text.encodeIllegalXMLCharacters(request.getContextPath()) %></li>
+  <li>Resource Path Prefix: <%= Text.encodeIllegalXMLCharacters(JCRWebdavServerServlet.getPathPrefix(pageContext.getServletContext())) %></li>
+  <li>Workspace Name: <i>optional</i> (available workspaces are mapped as resources)</li>
+  <li>Additional servlet configuration: see <i>/WEB-INF/web.xml</i></li>
+</ul>
+<jsp:include page="footer.jsp"/>
diff --git a/jackrabbit-webapp/src/main/webapp/webdav-remoting.jsp b/jackrabbit-webapp/src/main/webapp/webdav-remoting.jsp
index c579c34..7e2514d 100644
--- a/jackrabbit-webapp/src/main/webapp/webdav-remoting.jsp
+++ b/jackrabbit-webapp/src/main/webapp/webdav-remoting.jsp
@@ -21,8 +21,9 @@
 request.setAttribute("title", "JCR Remoting Server with Batch Read/Write");
 
 URI uri = new URI(request.getRequestURL().toString());
+int port = uri.getPort();
 String href =
-    uri.getScheme() + "://" + uri.getHost() + ":" + uri.getPort()
+    uri.getScheme() + "://" + uri.getHost() + (port == -1 ? "" : (":" + port))
     + request.getContextPath()
     + JCRWebdavServerServlet.getPathPrefix(pageContext.getServletContext());
 href = Text.encodeIllegalXMLCharacters(href);
diff --git a/jackrabbit-webapp/src/main/webapp/webdav-simple.jsp b/jackrabbit-webapp/src/main/webapp/webdav-simple.jsp
index d6b6dfd..587cc41 100644
--- a/jackrabbit-webapp/src/main/webapp/webdav-simple.jsp
+++ b/jackrabbit-webapp/src/main/webapp/webdav-simple.jsp
@@ -19,8 +19,9 @@
 request.setAttribute("title", "Standard WebDAV Server");
 
 URI uri = new URI(request.getRequestURL().toString());
+int port = uri.getPort();
 String href =
-    uri.getScheme() + "://" + uri.getHost() + ":" + uri.getPort()
+    uri.getScheme() + "://" + uri.getHost() + (port == -1 ? "" : (":" + port))
     + request.getContextPath()
     + SimpleWebdavServlet.getPathPrefix(pageContext.getServletContext())
     + "/default/";
diff --git a/jackrabbit-webapp/src/test/java/org/apache/jackrabbit/j2ee/BackwardsCompatibilityIT.java b/jackrabbit-webapp/src/test/java/org/apache/jackrabbit/j2ee/BackwardsCompatibilityIT.java
new file mode 100644
index 0000000..398f815
--- /dev/null
+++ b/jackrabbit-webapp/src/test/java/org/apache/jackrabbit/j2ee/BackwardsCompatibilityIT.java
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.j2ee;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Enumeration;
+import java.util.Random;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionHistory;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.derby.jdbc.EmbeddedDriver;
+import org.apache.jackrabbit.core.RepositoryImpl;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.config.RepositoryConfig;
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.jcr.Jcr;
+import org.apache.jackrabbit.oak.plugins.segment.SegmentNodeStore;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.upgrade.RepositoryUpgrade;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BackwardsCompatibilityIT extends TestCase {
+
+    /**
+     * Logger instance
+     */
+    private static final Logger log =
+        LoggerFactory.getLogger(BackwardsCompatibilityIT.class);
+
+    public void testBackwardsCompatibility() throws Exception {
+        // Force loading of the Derby JDBC driver
+        new EmbeddedDriver();
+
+        File target = new File("target/backwards-compatibility-test");
+        FileUtils.deleteDirectory(target);
+        target.mkdirs();
+
+        File source = new File("src/test/resources/compatibility.zip");
+        unpack(source, target);
+
+        for (File dir : target.listFiles()) {
+            if (dir.isDirectory()) {
+                log.info("Testing backwards compatibility with {}", dir);
+                checkJackrabbitRepository(dir);
+
+                NodeStore store = new SegmentNodeStore();
+                RepositoryUpgrade.copy(dir, store);
+                checkRepositoryContent(
+                        new Jcr(new Oak(store)).createRepository());
+            }
+        }
+    }
+
+    private void checkJackrabbitRepository(File directory) throws Exception {
+        File configuration = new File(directory, "repository.xml");
+
+        try {
+            RepositoryConfig config = RepositoryConfig.create(
+                    configuration.getPath(), directory.getPath());
+            RepositoryImpl repository = RepositoryImpl.create(config);
+            try {
+                checkRepositoryContent(repository);
+            } finally {
+                repository.shutdown();
+            }
+        } catch (RepositoryException e) {
+            String message = "Unable to access repository " + directory;
+            log.error(message, e);
+            fail(message);
+        }
+    }
+
+    private void checkRepositoryContent(Repository repository)
+            throws Exception {
+        Session session = repository.login(
+                new SimpleCredentials("admin", "admin".toCharArray()));
+        try {
+            assertTestData(session);
+        } finally {
+            session.logout();
+        }
+    }
+
+    private void assertTestData(Session session) throws Exception {
+        Node root = session.getRootNode();
+
+        assertTrue(root.hasNode("test"));
+        Node test = root.getNode("test");
+
+        Node versionable = assertVersionable(test);
+        assertProperties(test, versionable);
+        if (session instanceof SessionImpl) {
+            // FIXME: Not yet supported by Oak
+            assertVersionableCopy(test, versionable);
+        }
+        assertLock(test);
+        assertUsers(session);
+    }
+
+    @SuppressWarnings("deprecation")
+    private Node assertVersionable(Node test) throws RepositoryException {
+        assertTrue(test.hasNode("versionable"));
+        Node versionable = test.getNode("versionable");
+        assertTrue(versionable.isNodeType("nt:myversionable"));
+        assertTrue(versionable.isNodeType("nt:unstructured"));
+        assertTrue(versionable.isNodeType("mix:versionable"));
+        assertFalse(versionable.isCheckedOut());
+
+        VersionHistory history = versionable.getVersionHistory();
+        Version versionB = versionable.getBaseVersion();
+        String[] labels = history.getVersionLabels(versionB);
+        assertEquals(1, labels.length);
+        assertEquals("labelB", labels[0]);
+        Version versionA = history.getVersionByLabel("labelA");
+        versionable.restore(versionA, true);
+        assertEquals("A", versionable.getProperty("foo").getString());
+        versionable.restore(versionB, true);
+        assertEquals("B", versionable.getProperty("foo").getString());
+        return versionable;
+    }
+
+    @SuppressWarnings("deprecation")
+    private void assertProperties(Node test, Node versionable)
+            throws RepositoryException, PathNotFoundException,
+            ValueFormatException, IOException {
+        assertTrue(test.hasNode("properties"));
+        Node properties = test.getNode("properties");
+        assertTrue(properties.isNodeType("nt:unstructured"));
+
+        assertEquals(true, properties.getProperty("boolean").getBoolean());
+        assertEquals(0.123456789, properties.getProperty("double").getDouble());
+        assertEquals(1234567890, properties.getProperty("long").getLong());
+        Node reference = properties.getProperty("reference").getNode();
+        assertTrue(reference.isSame(versionable));
+        assertEquals("test", properties.getProperty("string").getString());
+
+        Value[] multiple = properties.getProperty("multiple").getValues();
+        assertEquals(3, multiple.length);
+        assertEquals("a", multiple[0].getString());
+        assertEquals("b", multiple[1].getString());
+        assertEquals("c", multiple[2].getString());
+
+        Calendar calendar = properties.getProperty("date").getDate();
+        assertEquals(1234567890, calendar.getTimeInMillis());
+
+        InputStream stream = properties.getProperty("binary").getStream();
+        try {
+            byte[] binary = new byte[100 * 1000];
+            new Random(1234567890).nextBytes(binary);
+            assertTrue(Arrays.equals(binary, IOUtils.toByteArray(stream)));
+        } finally {
+            stream.close();
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    private void assertVersionableCopy(Node test, Node versionable)
+            throws RepositoryException, IOException {
+        test.getSession().getWorkspace().copy(
+                versionable.getPath(),
+                versionable.getPath() + "-copy");
+        Node copy = test.getNode(versionable.getName() + "-copy");
+        copy.remove();
+        test.save();
+    }
+
+    private void assertLock(Node test) throws RepositoryException {
+        Node lock = test.getNode("lock");
+        assertTrue(lock.isLocked());
+        assertTrue(lock.hasProperty("jcr:lockOwner"));
+    }
+
+    private void assertUsers(Session session) throws RepositoryException {
+    }
+
+    private void unpack(File archive, File dir) throws IOException {
+        ZipFile zip = new ZipFile(archive);
+        try {
+            Enumeration<? extends ZipEntry> entries = zip.entries();
+            while (entries.hasMoreElements()) {
+                ZipEntry entry = entries.nextElement();
+                if (entry.getName().startsWith("META-INF")) {
+                } else if (entry.isDirectory()) {
+                    new File(dir, entry.getName()).mkdirs();
+                } else {
+                    File file = new File(dir, entry.getName());
+                    file.getParentFile().mkdirs();
+                    InputStream input = zip.getInputStream(entry);
+                    try {
+                        OutputStream output = new FileOutputStream(file);
+                        try {
+                            IOUtils.copy(input, output);
+                        } finally {
+                            output.close();
+                        }
+                    } finally {
+                        input.close();
+                    }
+                }
+            }
+        } finally {
+            zip.close();
+        }
+    }
+
+}
+
diff --git a/jackrabbit-webapp/src/test/java/org/apache/jackrabbit/j2ee/TomcatIT.java b/jackrabbit-webapp/src/test/java/org/apache/jackrabbit/j2ee/TomcatIT.java
index cc22f50..158bcbf 100644
--- a/jackrabbit-webapp/src/test/java/org/apache/jackrabbit/j2ee/TomcatIT.java
+++ b/jackrabbit-webapp/src/test/java/org/apache/jackrabbit/j2ee/TomcatIT.java
@@ -16,16 +16,14 @@
  */
 package org.apache.jackrabbit.j2ee;
 
+import java.io.File;
 import java.io.IOException;
 import java.net.URL;
 
 import junit.framework.TestCase;
 
-import org.apache.catalina.Engine;
-import org.apache.catalina.Host;
-import org.apache.catalina.connector.Connector;
-import org.apache.catalina.core.StandardContext;
-import org.apache.catalina.startup.Embedded;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.commons.io.FileUtils;
 import org.slf4j.bridge.SLF4JBridgeHandler;
 
 import com.gargoylesoftware.htmlunit.WebClient;
@@ -36,42 +34,47 @@ import com.gargoylesoftware.htmlunit.html.HtmlPage;
 
 public class TomcatIT extends TestCase {
 
+    static {
+        SLF4JBridgeHandler.install();
+    }
+
     private URL url;
 
-    private Embedded tomcat;
+    private Tomcat tomcat;
 
     private WebClient client;
 
     protected void setUp() throws Exception {
-        SLF4JBridgeHandler.install();
-
-        url = new URL("http://localhost:12856/");
+        File war = null;
+        for (File f : new File("target").listFiles()) {
+            if (f.isDirectory() && new File(f, "WEB-INF/web.xml").isFile()) {
+                war = f;
+                break;
+            }
+        }
+        assertNotNull(war);
 
-        tomcat = new Embedded();
-        tomcat.setCatalinaBase("tomcat");
-        tomcat.setCatalinaHome("tomcat");
+        File bootstrap = new File("target", "bootstrap.properties");
+        bootstrap.delete();
+        RepositoryAccessServlet.bootstrapOverride = bootstrap.getPath();
+        RepositoryStartupServlet.bootstrapOverride = bootstrap.getPath();
 
-        Engine engine = tomcat.createEngine();
-        engine.setName("localengine");
-        engine.setDefaultHost(url.getHost());
+        File baseDir = new File("target", "tomcat");
+        FileUtils.deleteQuietly(baseDir);
 
-        Host host = tomcat.createHost(url.getHost(), "webapps");
-        host.setAutoDeploy(false);
-        engine.addChild(host);
+        File repoDir = new File("target", "repository");
+        FileUtils.deleteQuietly(repoDir);
 
-        String webapp = System.getProperty("webapp.directory");
-        StandardContext context =
-            (StandardContext) tomcat.createContext("", webapp);
-        context.setDefaultWebXml(System.getProperty("default.webxml"));
-        host.addChild(context);
+        url = new URL("http://localhost:12856/");
 
-        tomcat.addEngine(engine);
+        tomcat = new Tomcat();
+        tomcat.setSilent(true);
+        tomcat.setBaseDir(baseDir.getPath());
+        tomcat.setHostname(url.getHost());
+        tomcat.setPort(url.getPort());
 
-        Connector connector =
-            tomcat.createConnector(url.getHost(), url.getPort(), false);
-        tomcat.addConnector(connector);
+        tomcat.addWebapp("", war.getAbsolutePath());
 
-        tomcat.setAwait(true);
         tomcat.start();
 
         client = new WebClient();
@@ -93,7 +96,7 @@ public class TomcatIT extends TestCase {
             for (HtmlInput mode : form.getInputsByName("mode")) {
                 if ("new".equals(mode.getValueAttribute())) {
                     for (HtmlInput home : form.getInputsByName("repository_home")) {
-                        home.setValueAttribute("repository");
+                        home.setValueAttribute("target/repository");
                         for (HtmlElement submit : form.getElementsByAttribute("input", "type", "submit")) {
                             return submit.click();
                         }
diff --git a/jackrabbit-webapp/src/test/resources/compatibility.zip b/jackrabbit-webapp/src/test/resources/compatibility.zip
new file mode 100644
index 0000000..8141883
Binary files /dev/null and b/jackrabbit-webapp/src/test/resources/compatibility.zip differ
diff --git a/jackrabbit-webapp/src/test/resources/default-web.xml b/jackrabbit-webapp/src/test/resources/default-web.xml
deleted file mode 100644
index 7839ccd..0000000
--- a/jackrabbit-webapp/src/test/resources/default-web.xml
+++ /dev/null
@@ -1,1205 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements.  See the NOTICE file distributed with
-  this work for additional information regarding copyright ownership.
-  The ASF licenses this file to You under the Apache License, Version 2.0
-  (the "License"); you may not use this file except in compliance with
-  the License.  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
--->
-<web-app xmlns="http://java.sun.com/xml/ns/javaee"
-    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
-    version="2.5">
-
-  <!-- ======================== Introduction ============================== -->
-  <!-- This document defines default values for *all* web applications      -->
-  <!-- loaded into this instance of Tomcat.  As each application is         -->
-  <!-- deployed, this file is processed, followed by the                    -->
-  <!-- "/WEB-INF/web.xml" deployment descriptor from your own               -->
-  <!-- applications.                                                        -->
-  <!--                                                                      -->
-  <!-- WARNING:  Do not configure application-specific resources here!      -->
-  <!-- They should go in the "/WEB-INF/web.xml" file in your application.   -->
-
-
-  <!-- ================== Built In Servlet Definitions ==================== -->
-
-
-  <!-- The default servlet for all web applications, that serves static     -->
-  <!-- resources.  It processes all requests that are not mapped to other   -->
-  <!-- servlets with servlet mappings (defined either here or in your own   -->
-  <!-- web.xml file.  This servlet supports the following initialization    -->
-  <!-- parameters (default values are in square brackets):                  -->
-  <!--                                                                      -->
-  <!--   debug               Debugging detail level for messages logged     -->
-  <!--                       by this servlet.  [0]                          -->
-  <!--                                                                      -->
-  <!--   fileEncoding        Encoding to be used to read static resources   -->
-  <!--                       [platform default]                             -->
-  <!--                                                                      -->
-  <!--   input               Input buffer size (in bytes) when reading      -->
-  <!--                       resources to be served.  [2048]                -->
-  <!--                                                                      -->
-  <!--   listings            Should directory listings be produced if there -->
-  <!--                       is no welcome file in this directory?  [false] -->
-  <!--                       WARNING: Listings for directories with many    -->
-  <!--                       entries can be slow and may consume            -->
-  <!--                       significant proportions of server resources.   -->
-  <!--                                                                      -->
-  <!--   output              Output buffer size (in bytes) when writing     -->
-  <!--                       resources to be served.  [2048]                -->
-  <!--                                                                      -->
-  <!--   readonly            Is this context "read only", so HTTP           -->
-  <!--                       commands like PUT and DELETE are               -->
-  <!--                       rejected?  [true]                              -->
-  <!--                                                                      -->
-  <!--   readmeFile          File name to display with the directory        -->
-  <!--                       contents. [null]                               -->
-  <!--                                                                      -->
-  <!--   sendfileSize        If the connector used supports sendfile, this  -->
-  <!--                       represents the minimal file size in KB for     -->
-  <!--                       which sendfile will be used. Use a negative    -->
-  <!--                       value to always disable sendfile.  [48]        -->
-  <!--                                                                      -->
-  <!--   useAcceptRanges     Should the Accept-Ranges header be included    -->
-  <!--                       in responses where appropriate? [true]         -->
-  <!--                                                                      -->
-  <!--  For directory listing customization. Checks localXsltFile, then     -->
-  <!--  globalXsltFile, then defaults to original behavior.                 -->
-  <!--                                                                      -->
-  <!--   localXsltFile       Make directory listings an XML doc and         -->
-  <!--                       pass the result to this style sheet residing   -->
-  <!--                       in that directory. This overrides              -->
-  <!--                       contextXsltFile and globalXsltFile[null]       -->
-  <!--                                                                      -->
-  <!--   contextXsltFile     Make directory listings an XML doc and         -->
-  <!--                       pass the result to this style sheet which is   -->
-  <!--                       relative to the context root. This overrides   -->
-  <!--                       globalXsltFile[null]                           -->
-  <!--                                                                      -->
-  <!--   globalXsltFile      Site wide configuration version of             -->
-  <!--                       localXsltFile This argument is expected        -->
-  <!--                       to be a physical file. [null]                  -->
-  <!--                                                                      -->
-  <!--                                                                      -->
-
-    <servlet>
-        <servlet-name>default</servlet-name>
-        <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
-        <init-param>
-            <param-name>debug</param-name>
-            <param-value>0</param-value>
-        </init-param>
-        <init-param>
-            <param-name>listings</param-name>
-            <param-value>false</param-value>
-        </init-param>
-        <load-on-startup>1</load-on-startup>
-    </servlet>
-
-
-  <!-- This servlet has been deprecated due to security concerns. Servlets  -->
-  <!-- should be explicitly mapped in web.xml                               -->
-  <!--                                                                      -->
-  <!-- The "invoker" servlet, which executes anonymous servlet classes      -->
-  <!-- that have not been defined in a web.xml file.  Traditionally, this   -->
-  <!-- servlet is mapped to the URL pattern "/servlet/*", but you can map   -->
-  <!-- it to other patterns as well.  The extra path info portion of such a -->
-  <!-- request must be the fully qualified class name of a Java class that  -->
-  <!-- implements Servlet (or extends HttpServlet), or the servlet name     -->
-  <!-- of an existing servlet definition.     This servlet supports the     -->
-  <!-- following initialization parameters (default values are in square    -->
-  <!-- brackets):                                                           -->
-  <!--                                                                      -->
-  <!--   debug               Debugging detail level for messages logged     -->
-  <!--                       by this servlet.  [0]                          -->
-
-<!--
-    <servlet>
-        <servlet-name>invoker</servlet-name>
-        <servlet-class>
-          org.apache.catalina.servlets.InvokerServlet
-        </servlet-class>
-        <init-param>
-            <param-name>debug</param-name>
-            <param-value>0</param-value>
-        </init-param>
-        <load-on-startup>2</load-on-startup>
-    </servlet>
--->
-
-
-  <!-- The JSP page compiler and execution servlet, which is the mechanism  -->
-  <!-- used by Tomcat to support JSP pages.  Traditionally, this servlet    -->
-  <!-- is mapped to the URL pattern "*.jsp".  This servlet supports the     -->
-  <!-- following initialization parameters (default values are in square    -->
-  <!-- brackets):                                                           -->
-  <!--                                                                      -->
-  <!--   checkInterval       If development is false and checkInterval is   -->
-  <!--                       greater than zero, background compilations are -->
-  <!--                       enabled. checkInterval is the time in seconds  -->
-  <!--                       between checks to see if a JSP page (and its   -->
-  <!--                       dependent files) needs to  be recompiled. [0]  -->
-  <!--                                                                      -->
-  <!--   classdebuginfo      Should the class file be compiled with         -->
-  <!--                       debugging information?  [true]                 -->
-  <!--                                                                      -->
-  <!--   classpath           What class path should I use while compiling   -->
-  <!--                       generated servlets?  [Created dynamically      -->
-  <!--                       based on the current web application]          -->
-  <!--                                                                      -->
-  <!--   compiler            Which compiler Ant should use to compile JSP   -->
-  <!--                       pages.  See the jasper documentation for more  -->
-  <!--                       information.                                   -->
-  <!--                                                                      -->
-  <!--   compilerSourceVM    Compiler source VM. [1.5]                      -->
-  <!--                                                                      -->
-  <!--   compilerTargetVM    Compiler target VM. [1.5]                      -->
-  <!--                                                                      -->
-  <!--   development         Is Jasper used in development mode? If true,   -->
-  <!--                       the frequency at which JSPs are checked for    -->
-  <!--                       modification may be specified via the          -->
-  <!--                       modificationTestInterval parameter. [true]     -->
-  <!--                                                                      -->
-  <!--   displaySourceFragment                                              -->
-  <!--                       Should a source fragment be included in        -->
-  <!--                       exception messages? [true]                     -->
-  <!--                                                                      -->
-  <!--   dumpSmap            Should the SMAP info for JSR45 debugging be    -->
-  <!--                       dumped to a file? [false]                      -->
-  <!--                       False if suppressSmap is true                  -->
-  <!--                                                                      -->
-  <!--   enablePooling       Determines whether tag handler pooling is      -->
-  <!--                       enabled. This is a compilation option. It will -->
-  <!--                       not alter the behaviour of JSPs that have      -->
-  <!--                       already been compiled. [true]                  -->
-  <!--                                                                      -->
-  <!--   engineOptionsClass  Allows specifying the Options class used to    -->
-  <!--                       configure Jasper. If not present, the default  -->
-  <!--                       EmbeddedServletOptions will be used.           -->
-  <!--                                                                      -->
-  <!--   errorOnUseBeanInvalidClassAttribute                                -->
-  <!--                       Should Jasper issue an error when the value of -->
-  <!--                       the class attribute in an useBean action is    -->
-  <!--                       not a valid bean class?  [true]                -->
-  <!--                                                                      -->
-  <!--   fork                Tell Ant to fork compiles of JSP pages so that -->
-  <!--                       a separate JVM is used for JSP page compiles   -->
-  <!--                       from the one Tomcat is running in. [true]      -->
-  <!--                                                                      -->
-  <!--   genStrAsCharArray   Should text strings be generated as char       -->
-  <!--                       arrays, to improve performance in some cases?  -->
-  <!--                       [false]                                        -->
-  <!--                                                                      -->
-  <!--   ieClassId           The class-id value to be sent to Internet      -->
-  <!--                       Explorer when using <jsp:plugin> tags.         -->
-  <!--                       [clsid:8AD9C840-044E-11D1-B3E9-00805F499D93]   -->
-  <!--                                                                      -->
-  <!--   javaEncoding        Java file encoding to use for generating java  -->
-  <!--                       source files. [UTF8]                           -->
-  <!--                                                                      -->
-  <!--   keepgenerated       Should we keep the generated Java source code  -->
-  <!--                       for each page instead of deleting it? [true]   -->
-  <!--                                                                      -->
-  <!--   mappedfile          Should we generate static content with one     -->
-  <!--                       print statement per input line, to ease        -->
-  <!--                       debugging?  [true]                             -->
-  <!--                                                                      -->
-  <!--   modificationTestInterval                                           -->
-  <!--                       Causes a JSP (and its dependent files) to not  -->
-  <!--                       be checked for modification during the         -->
-  <!--                       specified time interval (in seconds) from the  -->
-  <!--                       last time the JSP was checked for              -->
-  <!--                       modification. A value of 0 will cause the JSP  -->
-  <!--                       to be checked on every access.                 -->
-  <!--                       Used in development mode only. [4]             -->
-  <!--                                                                      -->
-  <!--   recompileOnFail     If a JSP compilation fails should the          -->
-  <!--                       modificationTestInterval be ignored and the    -->
-  <!--                       next access trigger a re-compilation attempt?  -->
-  <!--                       Used in development mode only and is disabled  -->
-  <!--                       by default as compilation may be expensive and -->
-  <!--                       could lead to excessive resource usage.        -->
-  <!--                       [false]                                        -->
-  <!--                                                                      -->
-  <!--   scratchdir          What scratch directory should we use when      -->
-  <!--                       compiling JSP pages?  [default work directory  -->
-  <!--                       for the current web application]               -->
-  <!--                                                                      -->
-  <!--   suppressSmap        Should the generation of SMAP info for JSR45   -->
-  <!--                       debugging be suppressed?  [false]              -->
-  <!--                                                                      -->
-  <!--   trimSpaces          Should white spaces in template text between   -->
-  <!--                       actions or directives be trimmed?  [false]     -->
-  <!--                                                                      -->
-  <!--   xpoweredBy          Determines whether X-Powered-By response       -->
-  <!--                       header is added by generated servlet  [false]  -->
-  <!--                                                                      -->
-  <!-- If you wish to use Jikes to compile JSP pages:                       -->
-  <!--   Please see the "Using Jikes" section of the Jasper-HowTo           -->
-  <!--   page in the Tomcat documentation.                                  -->
-
-    <servlet>
-        <servlet-name>jsp</servlet-name>
-        <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
-        <init-param>
-            <param-name>fork</param-name>
-            <param-value>false</param-value>
-        </init-param>
-        <init-param>
-            <param-name>xpoweredBy</param-name>
-            <param-value>false</param-value>
-        </init-param>
-        <load-on-startup>3</load-on-startup>
-    </servlet>
-
-
-  <!-- NOTE: An SSI Filter is also available as an alternative SSI          -->
-  <!-- implementation. Use either the Servlet or the Filter but NOT both.   -->
-  <!--                                                                      -->
-  <!-- Server Side Includes processing servlet, which processes SSI         -->
-  <!-- directives in HTML pages consistent with similar support in web      -->
-  <!-- servers like Apache.  Traditionally, this servlet is mapped to the   -->
-  <!-- URL pattern "*.shtml".  This servlet supports the following          -->
-  <!-- initialization parameters (default values are in square brackets):   -->
-  <!--                                                                      -->
-  <!--   buffered            Should output from this servlet be buffered?   -->
-  <!--                       (0=false, 1=true)  [0]                         -->
-  <!--                                                                      -->
-  <!--   debug               Debugging detail level for messages logged     -->
-  <!--                       by this servlet.  [0]                          -->
-  <!--                                                                      -->
-  <!--   expires             The number of seconds before a page with SSI   -->
-  <!--                       directives will expire.  [No default]          -->
-  <!--                                                                      -->
-  <!--   isVirtualWebappRelative                                            -->
-  <!--                       Should "virtual" paths be interpreted as       -->
-  <!--                       relative to the context root, instead of       -->
-  <!--                       the server root?  (0=false, 1=true) [0]        -->
-  <!--                                                                      -->
-  <!--   inputEncoding       The encoding to assume for SSI resources if    -->
-  <!--                       one is not available from the resource.        -->
-  <!--                       [Platform default]                             -->
-  <!--                                                                      -->
-  <!--   outputEncoding      The encoding to use for the page that results  -->
-  <!--                       from the SSI processing. [UTF-8]               -->
-  <!--                                                                      -->
-  <!--   allowExec           Is use of the exec command enabled? [false]    -->
-
-<!--
-    <servlet>
-        <servlet-name>ssi</servlet-name>
-        <servlet-class>
-          org.apache.catalina.ssi.SSIServlet
-        </servlet-class>
-        <init-param>
-          <param-name>buffered</param-name>
-          <param-value>1</param-value>
-        </init-param>
-        <init-param>
-          <param-name>debug</param-name>
-          <param-value>0</param-value>
-        </init-param>
-        <init-param>
-          <param-name>expires</param-name>
-          <param-value>666</param-value>
-        </init-param>
-        <init-param>
-          <param-name>isVirtualWebappRelative</param-name>
-          <param-value>0</param-value>
-        </init-param>
-        <load-on-startup>4</load-on-startup>
-    </servlet>
--->
-
-
-  <!-- Common Gateway Includes (CGI) processing servlet, which supports     -->
-  <!-- execution of external applications that conform to the CGI spec      -->
-  <!-- requirements.  Typically, this servlet is mapped to the URL pattern  -->
-  <!-- "/cgi-bin/*", which means that any CGI applications that are         -->
-  <!-- executed must be present within the web application.  This servlet   -->
-  <!-- supports the following initialization parameters (default values     -->
-  <!-- are in square brackets):                                             -->
-  <!--                                                                      -->
-  <!--   cgiPathPrefix        The CGI search path will start at             -->
-  <!--                        webAppRootDir + File.separator + this prefix. -->
-  <!--                        [WEB-INF/cgi]                                 -->
-  <!--                                                                      -->
-  <!--   debug                Debugging detail level for messages logged    -->
-  <!--                        by this servlet.  [0]                         -->
-  <!--                                                                      -->
-  <!--   executable           Name of the executable used to run the        -->
-  <!--                        script. [perl]                                -->
-  <!--                                                                      -->
-  <!--   parameterEncoding    Name of parameter encoding to be used with    -->
-  <!--                        CGI servlet.                                  -->
-  <!--                        [System.getProperty("file.encoding","UTF-8")] -->
-  <!--                                                                      -->
-  <!--   passShellEnvironment Should the shell environment variables (if    -->
-  <!--                        any) be passed to the CGI script? [false]     -->
-  <!--                                                                      -->
-  <!--   stderrTimeout        The time (in milliseconds) to wait for the    -->
-  <!--                        reading of stderr to complete before          -->
-  <!--                        terminating the CGI process. [2000]           -->
-
-<!--
-    <servlet>
-        <servlet-name>cgi</servlet-name>
-        <servlet-class>org.apache.catalina.servlets.CGIServlet</servlet-class>
-        <init-param>
-          <param-name>debug</param-name>
-          <param-value>0</param-value>
-        </init-param>
-        <init-param>
-          <param-name>cgiPathPrefix</param-name>
-          <param-value>WEB-INF/cgi</param-value>
-        </init-param>
-         <load-on-startup>5</load-on-startup>
-    </servlet>
--->
-
-
-  <!-- ================ Built In Servlet Mappings ========================= -->
-
-
-  <!-- The servlet mappings for the built in servlets defined above.  Note  -->
-  <!-- that, by default, the CGI and SSI servlets are *not* mapped.  You    -->
-  <!-- must uncomment these mappings (or add them to your application's own -->
-  <!-- web.xml deployment descriptor) to enable these services              -->
-
-    <!-- The mapping for the default servlet -->
-    <servlet-mapping>
-        <servlet-name>default</servlet-name>
-        <url-pattern>/</url-pattern>
-    </servlet-mapping>
-
-    <!-- The mapping for the deprecated invoker servlet -->
-<!--
-    <servlet-mapping>
-        <servlet-name>invoker</servlet-name>
-        <url-pattern>/servlet/*</url-pattern>
-    </servlet-mapping>
--->
-
-    <!-- The mapping for the JSP servlet -->
-    <servlet-mapping>
-        <servlet-name>jsp</servlet-name>
-        <url-pattern>*.jsp</url-pattern>
-    </servlet-mapping>
-
-    <servlet-mapping>
-        <servlet-name>jsp</servlet-name>
-        <url-pattern>*.jspx</url-pattern>
-    </servlet-mapping>
-
-    <!-- The mapping for the SSI servlet -->
-<!--
-    <servlet-mapping>
-        <servlet-name>ssi</servlet-name>
-        <url-pattern>*.shtml</url-pattern>
-    </servlet-mapping>
--->
-
-    <!-- The mapping for the CGI Gateway servlet -->
-
-<!--
-    <servlet-mapping>
-        <servlet-name>cgi</servlet-name>
-        <url-pattern>/cgi-bin/*</url-pattern>
-    </servlet-mapping>
--->
-
-
-  <!-- ================== Built In Filter Definitions ===================== -->
-
-  <!-- NOTE: An SSI Servlet is also available as an alternative SSI         -->
-  <!-- implementation. Use either the Servlet or the Filter but NOT both.   -->
-  <!--                                                                      -->
-  <!-- Server Side Includes processing filter, which processes SSI          -->
-  <!-- directives in HTML pages consistent with similar support in web      -->
-  <!-- servers like Apache.  Traditionally, this filter is mapped to the    -->
-  <!-- URL pattern "*.shtml", though it can be mapped to "*" as it will     -->
-  <!-- selectively enable/disable SSI processing based on mime types. For   -->
-  <!-- this to work you will need to uncomment the .shtml mime type         -->
-  <!-- definition towards the bottom of this file.                          -->
-  <!-- The contentType init param allows you to apply SSI processing to JSP -->
-  <!-- pages, javascript, or any other content you wish.  This filter       -->
-  <!-- supports the following initialization parameters (default values are -->
-  <!-- in square brackets):                                                 -->
-  <!--                                                                      -->
-  <!--   contentType         A regex pattern that must be matched before    -->
-  <!--                       SSI processing is applied.                     -->
-  <!--                       [text/x-server-parsed-html(;.*)?]              -->
-  <!--                                                                      -->
-  <!--   debug               Debugging detail level for messages logged     -->
-  <!--                       by this servlet.  [0]                          -->
-  <!--                                                                      -->
-  <!--   expires             The number of seconds before a page with SSI   -->
-  <!--                       directives will expire.  [No default]          -->
-  <!--                                                                      -->
-  <!--   isVirtualWebappRelative                                            -->
-  <!--                       Should "virtual" paths be interpreted as       -->
-  <!--                       relative to the context root, instead of       -->
-  <!--                       the server root?  (0=false, 1=true) [0]        -->
-  <!--                                                                      -->
-  <!--   allowExec           Is use of the exec command enabled? [false]    -->
-
-<!--
-    <filter>
-        <filter-name>ssi</filter-name>
-        <filter-class>
-          org.apache.catalina.ssi.SSIFilter
-        </filter-class>
-        <init-param>
-          <param-name>contentType</param-name>
-          <param-value>text/x-server-parsed-html(;.*)?</param-value>
-        </init-param>
-        <init-param>
-          <param-name>debug</param-name>
-          <param-value>0</param-value>
-        </init-param>
-        <init-param>
-          <param-name>expires</param-name>
-          <param-value>666</param-value>
-        </init-param>
-        <init-param>
-          <param-name>isVirtualWebappRelative</param-name>
-          <param-value>0</param-value>
-        </init-param>
-    </filter>
--->
-
-
-  <!-- ==================== Built In Filter Mappings ====================== -->
-
-  <!-- The mapping for the SSI Filter -->
-<!--
-    <filter-mapping>
-        <filter-name>ssi</filter-name>
-        <url-pattern>*.shtml</url-pattern>
-    </filter-mapping>
--->
-
-
-  <!-- ==================== Default Session Configuration ================= -->
-  <!-- You can set the default session timeout (in minutes) for all newly   -->
-  <!-- created sessions by modifying the value below.                       -->
-
-    <session-config>
-        <session-timeout>30</session-timeout>
-    </session-config>
-
-
-  <!-- ===================== Default MIME Type Mappings =================== -->
-  <!-- When serving static resources, Tomcat will automatically generate    -->
-  <!-- a "Content-Type" header based on the resource's filename extension,  -->
-  <!-- based on these mappings.  Additional mappings can be added here (to  -->
-  <!-- apply to all web applications), or in your own application's web.xml -->
-  <!-- deployment descriptor.                                               -->
-
-    <mime-mapping>
-        <extension>abs</extension>
-        <mime-type>audio/x-mpeg</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>ai</extension>
-        <mime-type>application/postscript</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>aif</extension>
-        <mime-type>audio/x-aiff</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>aifc</extension>
-        <mime-type>audio/x-aiff</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>aiff</extension>
-        <mime-type>audio/x-aiff</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>aim</extension>
-        <mime-type>application/x-aim</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>art</extension>
-        <mime-type>image/x-jg</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>asf</extension>
-        <mime-type>video/x-ms-asf</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>asx</extension>
-        <mime-type>video/x-ms-asf</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>au</extension>
-        <mime-type>audio/basic</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>avi</extension>
-        <mime-type>video/x-msvideo</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>avx</extension>
-        <mime-type>video/x-rad-screenplay</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>bcpio</extension>
-        <mime-type>application/x-bcpio</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>bin</extension>
-        <mime-type>application/octet-stream</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>bmp</extension>
-        <mime-type>image/bmp</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>body</extension>
-        <mime-type>text/html</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>cdf</extension>
-        <mime-type>application/x-cdf</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>cer</extension>
-        <mime-type>application/x-x509-ca-cert</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>class</extension>
-        <mime-type>application/java</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>cpio</extension>
-        <mime-type>application/x-cpio</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>csh</extension>
-        <mime-type>application/x-csh</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>css</extension>
-        <mime-type>text/css</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>dib</extension>
-        <mime-type>image/bmp</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>doc</extension>
-        <mime-type>application/msword</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>dtd</extension>
-        <mime-type>application/xml-dtd</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>dv</extension>
-        <mime-type>video/x-dv</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>dvi</extension>
-        <mime-type>application/x-dvi</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>eps</extension>
-        <mime-type>application/postscript</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>etx</extension>
-        <mime-type>text/x-setext</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>exe</extension>
-        <mime-type>application/octet-stream</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>gif</extension>
-        <mime-type>image/gif</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>gtar</extension>
-        <mime-type>application/x-gtar</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>gz</extension>
-        <mime-type>application/x-gzip</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>hdf</extension>
-        <mime-type>application/x-hdf</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>hqx</extension>
-        <mime-type>application/mac-binhex40</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>htc</extension>
-        <mime-type>text/x-component</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>htm</extension>
-        <mime-type>text/html</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>html</extension>
-        <mime-type>text/html</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>hqx</extension>
-        <mime-type>application/mac-binhex40</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>ief</extension>
-        <mime-type>image/ief</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>jad</extension>
-        <mime-type>text/vnd.sun.j2me.app-descriptor</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>jar</extension>
-        <mime-type>application/java-archive</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>java</extension>
-        <mime-type>text/plain</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>jnlp</extension>
-        <mime-type>application/x-java-jnlp-file</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>jpe</extension>
-        <mime-type>image/jpeg</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>jpeg</extension>
-        <mime-type>image/jpeg</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>jpg</extension>
-        <mime-type>image/jpeg</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>js</extension>
-        <mime-type>text/javascript</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>jsf</extension>
-        <mime-type>text/plain</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>jspf</extension>
-        <mime-type>text/plain</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>kar</extension>
-        <mime-type>audio/x-midi</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>latex</extension>
-        <mime-type>application/x-latex</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>m3u</extension>
-        <mime-type>audio/x-mpegurl</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>mac</extension>
-        <mime-type>image/x-macpaint</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>man</extension>
-        <mime-type>application/x-troff-man</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>mathml</extension>
-        <mime-type>application/mathml+xml</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>me</extension>
-        <mime-type>application/x-troff-me</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>mid</extension>
-        <mime-type>audio/x-midi</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>midi</extension>
-        <mime-type>audio/x-midi</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>mif</extension>
-        <mime-type>application/x-mif</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>mov</extension>
-        <mime-type>video/quicktime</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>movie</extension>
-        <mime-type>video/x-sgi-movie</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>mp1</extension>
-        <mime-type>audio/x-mpeg</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>mp2</extension>
-        <mime-type>audio/x-mpeg</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>mp3</extension>
-        <mime-type>audio/x-mpeg</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>mp4</extension>
-        <mime-type>video/mp4</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>mpa</extension>
-        <mime-type>audio/x-mpeg</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>mpe</extension>
-        <mime-type>video/mpeg</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>mpeg</extension>
-        <mime-type>video/mpeg</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>mpega</extension>
-        <mime-type>audio/x-mpeg</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>mpg</extension>
-        <mime-type>video/mpeg</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>mpv2</extension>
-        <mime-type>video/mpeg2</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>ms</extension>
-        <mime-type>application/x-wais-source</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>nc</extension>
-        <mime-type>application/x-netcdf</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>oda</extension>
-        <mime-type>application/oda</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <!-- OpenDocument Database -->
-        <extension>odb</extension>
-        <mime-type>application/vnd.oasis.opendocument.database</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <!-- OpenDocument Chart -->
-        <extension>odc</extension>
-        <mime-type>application/vnd.oasis.opendocument.chart</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <!-- OpenDocument Formula -->
-        <extension>odf</extension>
-        <mime-type>application/vnd.oasis.opendocument.formula</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <!-- OpenDocument Drawing -->
-        <extension>odg</extension>
-        <mime-type>application/vnd.oasis.opendocument.graphics</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <!-- OpenDocument Image -->
-        <extension>odi</extension>
-        <mime-type>application/vnd.oasis.opendocument.image</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <!-- OpenDocument Master Document -->
-        <extension>odm</extension>
-        <mime-type>application/vnd.oasis.opendocument.text-master</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <!-- OpenDocument Presentation -->
-        <extension>odp</extension>
-        <mime-type>application/vnd.oasis.opendocument.presentation</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <!-- OpenDocument Spreadsheet -->
-        <extension>ods</extension>
-        <mime-type>application/vnd.oasis.opendocument.spreadsheet</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <!-- OpenDocument Text -->
-        <extension>odt</extension>
-        <mime-type>application/vnd.oasis.opendocument.text</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>ogg</extension>
-        <mime-type>application/ogg</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <!-- OpenDocument Drawing Template -->
-        <extension>otg </extension>
-        <mime-type>application/vnd.oasis.opendocument.graphics-template</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <!-- HTML Document Template -->
-        <extension>oth</extension>
-        <mime-type>application/vnd.oasis.opendocument.text-web</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <!-- OpenDocument Presentation Template -->
-        <extension>otp</extension>
-        <mime-type>application/vnd.oasis.opendocument.presentation-template</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <!-- OpenDocument Spreadsheet Template -->
-        <extension>ots</extension>
-        <mime-type>application/vnd.oasis.opendocument.spreadsheet-template </mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <!-- OpenDocument Text Template -->
-        <extension>ott</extension>
-        <mime-type>application/vnd.oasis.opendocument.text-template</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>pbm</extension>
-        <mime-type>image/x-portable-bitmap</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>pct</extension>
-        <mime-type>image/pict</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>pdf</extension>
-        <mime-type>application/pdf</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>pgm</extension>
-        <mime-type>image/x-portable-graymap</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>pic</extension>
-        <mime-type>image/pict</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>pict</extension>
-        <mime-type>image/pict</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>pls</extension>
-        <mime-type>audio/x-scpls</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>png</extension>
-        <mime-type>image/png</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>pnm</extension>
-        <mime-type>image/x-portable-anymap</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>pnt</extension>
-        <mime-type>image/x-macpaint</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>ppm</extension>
-        <mime-type>image/x-portable-pixmap</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>ppt</extension>
-        <mime-type>application/vnd.ms-powerpoint</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>pps</extension>
-        <mime-type>application/vnd.ms-powerpoint</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>ps</extension>
-        <mime-type>application/postscript</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>psd</extension>
-        <mime-type>image/x-photoshop</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>qt</extension>
-        <mime-type>video/quicktime</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>qti</extension>
-        <mime-type>image/x-quicktime</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>qtif</extension>
-        <mime-type>image/x-quicktime</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>ras</extension>
-        <mime-type>image/x-cmu-raster</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>rdf</extension>
-        <mime-type>application/rdf+xml</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>rgb</extension>
-        <mime-type>image/x-rgb</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>rm</extension>
-        <mime-type>application/vnd.rn-realmedia</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>roff</extension>
-        <mime-type>application/x-troff</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>rtf</extension>
-        <mime-type>application/rtf</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>rtx</extension>
-        <mime-type>text/richtext</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>sh</extension>
-        <mime-type>application/x-sh</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>shar</extension>
-        <mime-type>application/x-shar</mime-type>
-    </mime-mapping>
-<!--
-    <mime-mapping>
-        <extension>shtml</extension>
-        <mime-type>text/x-server-parsed-html</mime-type>
-    </mime-mapping>
--->
-    <mime-mapping>
-        <extension>smf</extension>
-        <mime-type>audio/x-midi</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>sit</extension>
-        <mime-type>application/x-stuffit</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>snd</extension>
-        <mime-type>audio/basic</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>src</extension>
-        <mime-type>application/x-wais-source</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>sv4cpio</extension>
-        <mime-type>application/x-sv4cpio</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>sv4crc</extension>
-        <mime-type>application/x-sv4crc</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>svg</extension>
-        <mime-type>image/svg+xml</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>svgz</extension>
-        <mime-type>image/svg+xml</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>swf</extension>
-        <mime-type>application/x-shockwave-flash</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>t</extension>
-        <mime-type>application/x-troff</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>tar</extension>
-        <mime-type>application/x-tar</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>tcl</extension>
-        <mime-type>application/x-tcl</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>tex</extension>
-        <mime-type>application/x-tex</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>texi</extension>
-        <mime-type>application/x-texinfo</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>texinfo</extension>
-        <mime-type>application/x-texinfo</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>tif</extension>
-        <mime-type>image/tiff</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>tiff</extension>
-        <mime-type>image/tiff</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>tr</extension>
-        <mime-type>application/x-troff</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>tsv</extension>
-        <mime-type>text/tab-separated-values</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>txt</extension>
-        <mime-type>text/plain</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>ulw</extension>
-        <mime-type>audio/basic</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>ustar</extension>
-        <mime-type>application/x-ustar</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>vxml</extension>
-        <mime-type>application/voicexml+xml</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>xbm</extension>
-        <mime-type>image/x-xbitmap</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>xht</extension>
-        <mime-type>application/xhtml+xml</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>xhtml</extension>
-        <mime-type>application/xhtml+xml</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>xls</extension>
-        <mime-type>application/vnd.ms-excel</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>xml</extension>
-        <mime-type>application/xml</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>xpm</extension>
-        <mime-type>image/x-xpixmap</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>xsl</extension>
-        <mime-type>application/xml</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>xslt</extension>
-        <mime-type>application/xslt+xml</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>xul</extension>
-        <mime-type>application/vnd.mozilla.xul+xml</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>xwd</extension>
-        <mime-type>image/x-xwindowdump</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>vsd</extension>
-        <mime-type>application/x-visio</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>wav</extension>
-        <mime-type>audio/x-wav</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <!-- Wireless Bitmap -->
-        <extension>wbmp</extension>
-        <mime-type>image/vnd.wap.wbmp</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <!-- WML Source -->
-        <extension>wml</extension>
-        <mime-type>text/vnd.wap.wml</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <!-- Compiled WML -->
-        <extension>wmlc</extension>
-        <mime-type>application/vnd.wap.wmlc</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <!-- WML Script Source -->
-        <extension>wmls</extension>
-        <mime-type>text/vnd.wap.wmlscript</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <!-- Compiled WML Script -->
-        <extension>wmlscriptc</extension>
-        <mime-type>application/vnd.wap.wmlscriptc</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>wmv</extension>
-        <mime-type>video/x-ms-wmv</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>wrl</extension>
-        <mime-type>x-world/x-vrml</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>wspolicy</extension>
-        <mime-type>application/wspolicy+xml</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>Z</extension>
-        <mime-type>application/x-compress</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>z</extension>
-        <mime-type>application/x-compress</mime-type>
-    </mime-mapping>
-    <mime-mapping>
-        <extension>zip</extension>
-        <mime-type>application/zip</mime-type>
-    </mime-mapping>
-
-  <!-- ==================== Default Welcome File List ===================== -->
-  <!-- When a request URI refers to a directory, the default servlet looks  -->
-  <!-- for a "welcome file" within that directory and, if present,          -->
-  <!-- to the corresponding resource URI for display.  If no welcome file   -->
-  <!-- is present, the default servlet either serves a directory listing,   -->
-  <!-- or returns a 404 status, depending on how it is configured.          -->
-  <!--                                                                      -->
-  <!-- If you define welcome files in your own application's web.xml        -->
-  <!-- deployment descriptor, that list *replaces* the list configured      -->
-  <!-- here, so be sure that you include any of the default values that     -->
-  <!-- you wish to include.                                                 -->
-
-    <welcome-file-list>
-        <welcome-file>index.html</welcome-file>
-        <welcome-file>index.htm</welcome-file>
-        <welcome-file>index.jsp</welcome-file>
-    </welcome-file-list>
-
-</web-app>
diff --git a/jackrabbit-webapp/src/test/resources/logback-test.xml b/jackrabbit-webapp/src/test/resources/logback-test.xml
index fcc74e9..15d98f9 100644
--- a/jackrabbit-webapp/src/test/resources/logback-test.xml
+++ b/jackrabbit-webapp/src/test/resources/logback-test.xml
@@ -24,7 +24,7 @@
     </encoder>
   </appender>
 
-  <root level="DEBUG">
+  <root level="INFO">
     <appender-ref ref="file"/>
   </root>
 
diff --git a/jackrabbit-webdav/pom.xml b/jackrabbit-webdav/pom.xml
index b82f756..b991326 100644
--- a/jackrabbit-webdav/pom.xml
+++ b/jackrabbit-webdav/pom.xml
@@ -26,7 +26,7 @@
   <parent>
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>jackrabbit-parent</artifactId>
-    <version>2.3.6</version>
+    <version>2.10.1</version>
     <relativePath>../jackrabbit-parent/pom.xml</relativePath>
   </parent>
   <artifactId>jackrabbit-webdav</artifactId>
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/AbstractLocatorFactory.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/AbstractLocatorFactory.java
index 2daf258..7aceb33 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/AbstractLocatorFactory.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/AbstractLocatorFactory.java
@@ -231,10 +231,10 @@ public abstract class AbstractLocatorFactory implements DavLocatorFactory {
                 buf.append(EncodeUtil.escapePath(resourcePath));
             }
             int length = buf.length();
-            if (length > 0 && buf.charAt(length - 1) != '/') {
+            if (length == 0 || (length > 0 && buf.charAt(length - 1) != '/')) {
                 buf.append("/");
             }
-            href = buf.toString();
+            this.href = buf.toString();
         }
 
         /**
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavConstants.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavConstants.java
index 010db65..a50b1cd 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavConstants.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavConstants.java
@@ -104,7 +104,7 @@ public interface DavConstants {
     public static final String XML_OWNER = "owner";
     /**
      * The <code>lockroot</code> XML element
-     * @see <a href="http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockroot">RFC 4818</a>
+     * @see <a href="http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockroot">RFC 4918</a>
      */
     public static final String XML_LOCKROOT = "lockroot";
 
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavException.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavException.java
index eb5431c..624f475 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavException.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavException.java
@@ -40,7 +40,7 @@ public class DavException extends Exception implements XmlSerializable {
         try {
             statusPhrases.load(DavException.class.getResourceAsStream("statuscode.properties"));
         } catch (IOException e) {
-            log.error("Failed to load status properties: "+ e.getMessage());
+            log.error("Failed to load status properties: " + e.getMessage());
         }
     }
 
@@ -128,7 +128,7 @@ public class DavException extends Exception implements XmlSerializable {
      * @return status phrase corresponding to the given error code.
      */
     public static String getStatusPhrase(int errorCode) {
-        return statusPhrases.getProperty(errorCode+"");
+        return statusPhrases.getProperty(errorCode + "");
     }
 
     /**
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/MultiStatusResponse.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/MultiStatusResponse.java
index 572a02b..a8704ed 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/MultiStatusResponse.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/MultiStatusResponse.java
@@ -149,7 +149,7 @@ public class MultiStatusResponse implements XmlSerializable, DavConstants {
      * properties according to the given <code>DavPropertyNameSet</code>. It
      * adds all known property to the '200' set, while unknown properties are
      * added to the '404' set.
-     * <p/>
+     * <p>
      * Note, that the set of property names is ignored in case of a {@link
      * #PROPFIND_ALL_PROP} and {@link #PROPFIND_PROPERTY_NAMES} propFindType.
      *
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/WebdavRequestImpl.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/WebdavRequestImpl.java
index ef75539..12b961d 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/WebdavRequestImpl.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/WebdavRequestImpl.java
@@ -98,18 +98,27 @@ public class WebdavRequestImpl implements WebdavRequest, DavConstants {
 
     /**
      * Creates a new <code>DavServletRequest</code> with the given parameters.
+     */
+    public WebdavRequestImpl(HttpServletRequest httpRequest, DavLocatorFactory factory) {
+        this(httpRequest, factory, true);
+    }
+
+    /**
+     * Creates a new <code>DavServletRequest</code> with the given parameters.
      *
      * @param httpRequest
      * @param factory
+     * @param createAbsoluteURI defines if we must create a absolute URI. if false a absolute path will be created
      */
-    public WebdavRequestImpl(HttpServletRequest httpRequest, DavLocatorFactory factory) {
+    public WebdavRequestImpl(HttpServletRequest httpRequest, DavLocatorFactory factory, boolean createAbsoluteURI) {
         this.httpRequest = httpRequest;
         this.factory = factory;
         this.ifHeader = new IfHeader(httpRequest);
 
         String host = getHeader("Host");
         String scheme = getScheme();
-        hrefPrefix = scheme + "://" + host + getContextPath();
+        String uriPrefix = scheme + "://" + host + getContextPath();
+        this.hrefPrefix = createAbsoluteURI ? uriPrefix : getContextPath();
     }
 
     /**
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/BindServletRequest.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/BindServletRequest.java
index 658301d..8d6c516 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/BindServletRequest.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/BindServletRequest.java
@@ -1,65 +1,65 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.webdav.bind;
-
-import org.apache.jackrabbit.webdav.DavException;
-import org.apache.jackrabbit.webdav.DavResourceLocator;
-
-/**
- * <code>BindServletRequest</code> provides extension useful for functionality
- * related to BIND specification.
- */
-public interface BindServletRequest {
-
-    /**
-     * Returns the {@link RebindInfo} present with the request
-     *
-     * @return {@link RebindInfo} object
-     * @throws org.apache.jackrabbit.webdav.DavException in case of an invalid or missing request body
-     */
-    public RebindInfo getRebindInfo() throws DavException;
-
-    /**
-     * Returns the {@link UnbindInfo} present with the request
-     *
-     * @return {@link UnbindInfo} object
-     * @throws org.apache.jackrabbit.webdav.DavException in case of an invalid or missing request body
-     */
-    public UnbindInfo getUnbindInfo() throws DavException;
-
-    /**
-     * Returns the {@link BindInfo} present with the request
-     *
-     * @return {@link BindInfo} object
-     * @throws org.apache.jackrabbit.webdav.DavException in case of an invalid or missing request body
-     */
-    public BindInfo getBindInfo() throws DavException;
-
-    /**
-     * Parses a href and returns the path of the resource.
-     *
-     * @return path of the resource identified by the href.
-     */
-    public DavResourceLocator getHrefLocator(String href) throws DavException;
-
-    /**
-     * Returns the path of the member resource of the request resource which is identified by the segment parameter.
-     *
-     * @return path of internal member resource.
-     */
-    public DavResourceLocator getMemberLocator(String segment);
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.bind;
+
+import org.apache.jackrabbit.webdav.DavException;
+import org.apache.jackrabbit.webdav.DavResourceLocator;
+
+/**
+ * <code>BindServletRequest</code> provides extension useful for functionality
+ * related to BIND specification.
+ */
+public interface BindServletRequest {
+
+    /**
+     * Returns the {@link RebindInfo} present with the request
+     *
+     * @return {@link RebindInfo} object
+     * @throws org.apache.jackrabbit.webdav.DavException in case of an invalid or missing request body
+     */
+    public RebindInfo getRebindInfo() throws DavException;
+
+    /**
+     * Returns the {@link UnbindInfo} present with the request
+     *
+     * @return {@link UnbindInfo} object
+     * @throws org.apache.jackrabbit.webdav.DavException in case of an invalid or missing request body
+     */
+    public UnbindInfo getUnbindInfo() throws DavException;
+
+    /**
+     * Returns the {@link BindInfo} present with the request
+     *
+     * @return {@link BindInfo} object
+     * @throws org.apache.jackrabbit.webdav.DavException in case of an invalid or missing request body
+     */
+    public BindInfo getBindInfo() throws DavException;
+
+    /**
+     * Parses a href and returns the path of the resource.
+     *
+     * @return path of the resource identified by the href.
+     */
+    public DavResourceLocator getHrefLocator(String href) throws DavException;
+
+    /**
+     * Returns the path of the member resource of the request resource which is identified by the segment parameter.
+     *
+     * @return path of internal member resource.
+     */
+    public DavResourceLocator getMemberLocator(String segment);
+}
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/RebindMethod.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/RebindMethod.java
index 4f05763..04a629b 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/RebindMethod.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/RebindMethod.java
@@ -1,54 +1,54 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.webdav.client.methods;
-
-import org.apache.jackrabbit.webdav.bind.RebindInfo;
-import org.apache.jackrabbit.webdav.DavServletResponse;
-
-import java.io.IOException;
-
-/**
- * <code>RebindMethod</code> replaces a binding to a resource (atomic version of move).
- */
-public class RebindMethod extends DavMethodBase {
-
-    public RebindMethod(String uri, RebindInfo info) throws IOException {
-        super(uri);
-        setRequestBody(info);
-    }
-
-    //---------------------------------------------------------< HttpMethod >---
-    /**
-     * @see org.apache.commons.httpclient.HttpMethod#getName()
-     */
-    @Override
-    public String getName() {
-        return "REBIND";
-    }
-
-    //------------------------------------------------------< DavMethodBase >---
-    /**
-     * @param statusCode
-     * @return true if status code is 200 (existing binding was overwritten) or
-     * 201 (new binding created).
-     */
-    @Override
-    protected boolean isSuccess(int statusCode) {
-        return statusCode == DavServletResponse.SC_CREATED || statusCode == DavServletResponse.SC_OK;
-    }
-}
-
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.client.methods;
+
+import org.apache.jackrabbit.webdav.bind.RebindInfo;
+import org.apache.jackrabbit.webdav.DavServletResponse;
+
+import java.io.IOException;
+
+/**
+ * <code>RebindMethod</code> replaces a binding to a resource (atomic version of move).
+ */
+public class RebindMethod extends DavMethodBase {
+
+    public RebindMethod(String uri, RebindInfo info) throws IOException {
+        super(uri);
+        setRequestBody(info);
+    }
+
+    //---------------------------------------------------------< HttpMethod >---
+    /**
+     * @see org.apache.commons.httpclient.HttpMethod#getName()
+     */
+    @Override
+    public String getName() {
+        return "REBIND";
+    }
+
+    //------------------------------------------------------< DavMethodBase >---
+    /**
+     * @param statusCode
+     * @return true if status code is 200 (existing binding was overwritten) or
+     * 201 (new binding created).
+     */
+    @Override
+    protected boolean isSuccess(int statusCode) {
+        return statusCode == DavServletResponse.SC_CREATED || statusCode == DavServletResponse.SC_OK;
+    }
+}
+
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/io/package-info.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/io/package-info.java
index 85d53ba..e4268bc 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/io/package-info.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/io/package-info.java
@@ -14,5 +14,5 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- at aQute.bnd.annotation.Version("1.0.0")
+ at aQute.bnd.annotation.Version("1.0.1")
 package org.apache.jackrabbit.webdav.io;
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/lock/AbstractActiveLock.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/lock/AbstractActiveLock.java
index 67035c2..abd3ef2 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/lock/AbstractActiveLock.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/lock/AbstractActiveLock.java
@@ -27,7 +27,7 @@ import org.w3c.dom.Element;
 public abstract class AbstractActiveLock implements ActiveLock, DavConstants {
 
     private String lockroot;
-    
+
     /**
      * @see ActiveLock#getLockroot()
      */
@@ -44,7 +44,7 @@ public abstract class AbstractActiveLock implements ActiveLock, DavConstants {
 
     /**
      * Returns the default Xml representation of the 'activelock' element
-     * as defined by RFC 2518.
+     * as defined by RFC 4918.
      *
      * @return Xml representation
      * @param document
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/lock/ActiveLock.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/lock/ActiveLock.java
index 3852508..c0ec6f6 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/lock/ActiveLock.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/lock/ActiveLock.java
@@ -96,7 +96,7 @@ public interface ActiveLock extends XmlSerializable {
      * Returns the lockroot.
      *
      * @return lockroot
-     * @see <a href="http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockroot">RFC 4818</a>
+     * @see <a href="http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockroot">RFC 4918</a>
      */
     public String getLockroot();
 
@@ -104,7 +104,7 @@ public interface ActiveLock extends XmlSerializable {
      * Set the lockroot.
      *
      * @param lockroot
-     * @see <a href="http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockroot">RFC 4818</a>
+     * @see <a href="http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockroot">RFC 4918</a>
      */
     public void setLockroot(String lockroot);
 
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/observation/ObservationConstants.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/observation/ObservationConstants.java
index 7bfa2f6..f103c5d 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/observation/ObservationConstants.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/observation/ObservationConstants.java
@@ -16,6 +16,8 @@
  */
 package org.apache.jackrabbit.webdav.observation;
 
+import javax.xml.namespace.QName;
+
 import org.apache.jackrabbit.webdav.property.DavPropertyName;
 import org.apache.jackrabbit.webdav.xml.Namespace;
 
@@ -78,7 +80,18 @@ public interface ObservationConstants {
     public static final String XML_EVENTINFO = "eventinfo";
     public static final String XML_EVENTPRIMARNODETYPE = "eventprimarynodetype";
     public static final String XML_EVENTMIXINNODETYPE = "eventmixinnodetype";
-    
+
+    public static final QName N_EVENT = new QName(NAMESPACE.getURI(), XML_EVENT);
+    public static final QName N_EVENTBUNDLE = new QName(NAMESPACE.getURI(), XML_EVENTBUNDLE);
+    public static final QName N_EVENTDATE = new QName(NAMESPACE.getURI(), XML_EVENTDATE);
+    public static final QName N_EVENTDISCOVERY = new QName(NAMESPACE.getURI(), XML_EVENTDISCOVERY);
+    public static final QName N_EVENTINFO = new QName(NAMESPACE.getURI(), XML_EVENTINFO);
+    public static final QName N_EVENTMIXINNODETYPE = new QName(NAMESPACE.getURI(), XML_EVENTMIXINNODETYPE);
+    public static final QName N_EVENTPRIMARYNODETYPE = new QName(NAMESPACE.getURI(), XML_EVENTPRIMARNODETYPE);
+    public static final QName N_EVENTTYPE = new QName(NAMESPACE.getURI(), XML_EVENTTYPE);
+    public static final QName N_EVENTUSERDATA = new QName(NAMESPACE.getURI(), XML_EVENTUSERDATA);
+    public static final QName N_EVENTUSERID = new QName(NAMESPACE.getURI(), XML_EVENTUSERID);
+
     //---< Property Names >-----------------------------------------------------
     /**
      * The protected subscription discovery property is used to find out about
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/package-info.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/package-info.java
index c0b741b..a27ad0b 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/package-info.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/package-info.java
@@ -14,5 +14,5 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- at aQute.bnd.annotation.Version("1.0.0")
+ at aQute.bnd.annotation.Version("1.0.1")
 package org.apache.jackrabbit.webdav;
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/property/DefaultDavProperty.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/property/DefaultDavProperty.java
index 2f90706..3defacc 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/property/DefaultDavProperty.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/property/DefaultDavProperty.java
@@ -45,12 +45,12 @@ public class DefaultDavProperty<T> extends AbstractDavProperty<T> {
      * @param name the name of the property
      * @param value the value of the property
      * @param namespace the namespace of the property
-     * @param isProtected A value of true, defines this property to be protected.
+     * @param isInvisibleInAllprop A value of true, defines this property to be protected.
      * It will not be returned in a {@link org.apache.jackrabbit.webdav.DavConstants#PROPFIND_ALL_PROP DAV:allprop}
      * PROPFIND request and cannot be set/removed with a PROPPATCH request.
      */
-    public DefaultDavProperty(String name, T value, Namespace namespace, boolean isProtected) {
-        super(DavPropertyName.create(name, namespace), isProtected);
+    public DefaultDavProperty(String name, T value, Namespace namespace, boolean isInvisibleInAllprop) {
+        super(DavPropertyName.create(name, namespace), isInvisibleInAllprop);
         this.value = value;
     }
 
@@ -73,12 +73,12 @@ public class DefaultDavProperty<T> extends AbstractDavProperty<T> {
      *
      * @param name the name of the property
      * @param value the value of the property
-     * @param isProtected A value of true, defines this property to be protected.
+     * @param isInvisibleInAllprop A value of true, defines this property to be protected.
      * It will not be returned in a {@link org.apache.jackrabbit.webdav.DavConstants#PROPFIND_ALL_PROP DAV:allprop}
      * PROPFIND request and cannot be set/removed with a PROPPATCH request.
      */
-    public DefaultDavProperty(DavPropertyName name, T value, boolean isProtected) {
-        super(name, isProtected);
+    public DefaultDavProperty(DavPropertyName name, T value, boolean isInvisibleInAllprop) {
+        super(name, isInvisibleInAllprop);
         this.value = value;
     }
 
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/property/PropContainer.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/property/PropContainer.java
index 1d9377f..a86e55f 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/property/PropContainer.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/property/PropContainer.java
@@ -40,7 +40,7 @@ public abstract class PropContainer implements XmlSerializable, DavConstants {
      *
      * @param contentEntry
      * @return true if the object could be added; false otherwise
-     * @deprecated Use {@link #addContent(PropEntry) instead.
+     * @deprecated Use {@link #addContent(PropEntry)} instead.
      */
     public boolean addContent(Object contentEntry) {
         if (contentEntry instanceof PropEntry) {
@@ -57,8 +57,6 @@ public abstract class PropContainer implements XmlSerializable, DavConstants {
      *
      * @param contentEntry
      * @return true if the object could be added; false otherwise
-     * @param contentEntry
-     * @return
      */
     public abstract boolean addContent(PropEntry contentEntry);
 
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/search/SearchInfo.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/search/SearchInfo.java
index 3a822d5..da8a187 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/search/SearchInfo.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/search/SearchInfo.java
@@ -41,7 +41,7 @@ import java.util.HashSet;
  * NOTE: The query is expected to be represented by the text contained in the
  * Xml element specifying the query language, thus the 'basicsearch' defined
  * by the Webdav Search Internet Draft is not supported by this implementation.
- * <p/>
+ * <p>
  *
  * Example of a valid 'searchrequest' body
  * <pre>
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/SupportedPrivilege.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/SupportedPrivilege.java
index 69d0906..7c55c13 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/SupportedPrivilege.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/SupportedPrivilege.java
@@ -16,7 +16,13 @@
  */
 package org.apache.jackrabbit.webdav.security;
 
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.jackrabbit.webdav.DavException;
+import org.apache.jackrabbit.webdav.DavServletResponse;
 import org.apache.jackrabbit.webdav.xml.DomUtil;
+import org.apache.jackrabbit.webdav.xml.ElementIterator;
 import org.apache.jackrabbit.webdav.xml.Namespace;
 import org.apache.jackrabbit.webdav.xml.XmlSerializable;
 import org.w3c.dom.Document;
@@ -80,4 +86,54 @@ public class SupportedPrivilege implements XmlSerializable {
         }
         return spElem;
     }
+
+    public Privilege getPrivilege() {
+        return privilege;
+    }
+    
+    public boolean isAbstract() {
+        return isAbstract;
+    }
+    
+    public SupportedPrivilege[] getSupportedPrivileges() {
+        return supportedPrivileges;
+    }
+    
+    /**
+     * Factory method to create/retrieve a <code>SupportedPrivilege</code> from the given
+     * DAV:privilege element.
+     *
+     * @param privilege
+     * @return
+     */
+    static SupportedPrivilege getSupportedPrivilege(Element supportedPrivilege) throws DavException {
+        if (!DomUtil.matches(supportedPrivilege, XML_SUPPORTED_PRIVILEGE, SecurityConstants.NAMESPACE)) {
+            throw new DavException(DavServletResponse.SC_BAD_REQUEST, "DAV:supported-privilege element expected.");
+        }
+        boolean isAbstract = false;
+        Privilege privilege = null;
+        String description = null;
+        String descriptionLanguage = null;
+        List<SupportedPrivilege> sp = new ArrayList<SupportedPrivilege>();
+
+        ElementIterator children = DomUtil.getChildren(supportedPrivilege);
+        while (children.hasNext()) {
+            Element child = children.next();
+            if (child.getLocalName().equals(XML_ABSTRACT)) {
+                isAbstract = true;
+            } else if (child.getLocalName().equals(Privilege.XML_PRIVILEGE)) {
+                privilege = Privilege.getPrivilege(child);
+            } else if (child.getLocalName().equals(XML_DESCRIPTION)) {
+                description = child.getLocalName();
+                if (child.hasAttribute(descriptionLanguage)) {
+                    descriptionLanguage = child.getAttribute(descriptionLanguage);
+                }
+            } else if (child.getLocalName().equals(XML_SUPPORTED_PRIVILEGE)) {
+                sp.add(getSupportedPrivilege(child));
+            }
+        }
+        return new SupportedPrivilege(privilege, description,
+                                     descriptionLanguage, isAbstract,
+                                     sp.toArray(new SupportedPrivilege[sp.size()]));
+    }
 }
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/SupportedPrivilegeSetProperty.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/SupportedPrivilegeSetProperty.java
index f0d2ef6..6223d24 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/SupportedPrivilegeSetProperty.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/SupportedPrivilegeSetProperty.java
@@ -16,9 +16,15 @@
  */
 package org.apache.jackrabbit.webdav.security;
 
+import org.apache.jackrabbit.webdav.DavException;
+import org.apache.jackrabbit.webdav.DavServletResponse;
 import org.apache.jackrabbit.webdav.property.AbstractDavProperty;
+import org.apache.jackrabbit.webdav.property.DavProperty;
+import org.w3c.dom.Element;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Collections;
 
@@ -52,6 +58,28 @@ public class SupportedPrivilegeSetProperty extends AbstractDavProperty<List<Supp
         this.supportedPrivileges = supportedPrivileges;
     }
 
+    public SupportedPrivilegeSetProperty(DavProperty<?> p) throws DavException {
+        super(SecurityConstants.SUPPORTED_PRIVILEGE_SET, true);
+        if (!SecurityConstants.SUPPORTED_PRIVILEGE_SET.equals(getName())) {
+            throw new DavException(DavServletResponse.SC_BAD_REQUEST, "DAV:supported-privilege-set expected.");
+        }
+
+        List<SupportedPrivilege> supportedPrivs = new ArrayList<SupportedPrivilege>();
+        
+        for (Object obj : Collections.singletonList(p.getValue())) {
+            if (obj instanceof Element) {
+                supportedPrivs.add(SupportedPrivilege.getSupportedPrivilege((Element) obj));
+            } else if (obj instanceof Collection) {
+                for (Object entry : ((Collection<?>) obj)) {
+                    if (entry instanceof Element) {
+                        supportedPrivs.add(SupportedPrivilege.getSupportedPrivilege((Element) entry));
+                    }
+                }
+            }
+        }
+        supportedPrivileges = supportedPrivs.toArray(new SupportedPrivilege[supportedPrivs.size()]);
+    }
+
     /**
      * @return List of {@link SupportedPrivilege}s.
      */
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/report/AclPrincipalReport.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/report/AclPrincipalReport.java
index 81fe0ea..64c56a4 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/report/AclPrincipalReport.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/report/AclPrincipalReport.java
@@ -36,7 +36,7 @@ import java.util.Map;
  * The AclPrincipalReport report returns the requested property set
  * for all principals in the DAV:acl property, that are identified by http(s)
  * URLs or by a DAV:property principal.
- * <p/>
+ * <p>
  * The request body MUST be a DAV:acl-principal-prop-set XML element:
  * <pre>
  * <!ELEMENT acl-principal-prop-set ANY>
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/report/PrincipalMatchReport.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/report/PrincipalMatchReport.java
index 5c3bb0f..3d86b92 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/report/PrincipalMatchReport.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/report/PrincipalMatchReport.java
@@ -36,7 +36,7 @@ import org.w3c.dom.Element;
  * the request body must contain a DAV:self element, for the latter a
  * DAV:principal-property element which in turn specifies the property to
  * be examined.
- * <p/>
+ * <p>
  * The request body MUST be a DAV:principal-match XML element:
  * <pre>
  * <!ELEMENT principal-match ((principal-property | self), prop?)>
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/report/PrincipalSearchReport.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/report/PrincipalSearchReport.java
index 5505c1c..cc9c3da 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/report/PrincipalSearchReport.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/report/PrincipalSearchReport.java
@@ -49,17 +49,17 @@ import java.util.List;
  * searched inside the DAV:prop element and the query string inside the DAV:match
  * element. Multiple DAV:property-search elements or multiple elements within the
  * DAV:prop element will be interpreted with a logical AND.
- * <p/>
+ * <p>
  * <strong>DAV:prop</strong> lists the property names to be reported in the
  * response for each of the principle resources found.
- * <p/>
+ * <p>
  * <strong>DAV:apply-to-principal-collection-set</strong>: Optional empty element.
  * If present in the request body the search will be executed over all members
  * of the collections that are listed as values in the DAV:principal-collection-set
  * property present on the resource the report has been requested for.
  * Otherwise the search is limited to all members (at any depth) of that resource
  * itself.
- * <p/>
+ * <p>
  * The response body must contain a single DAV:multistatus XML element.
  */
 public class PrincipalSearchReport extends AbstractSecurityReport {
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/report/SearchablePropertyReport.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/report/SearchablePropertyReport.java
index f27b106..084b991 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/report/SearchablePropertyReport.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/security/report/SearchablePropertyReport.java
@@ -40,9 +40,9 @@ import java.util.Set;
  * searched using the {@link PrincipalSearchReport DAV:principal-property-search REPORT}.
  * This report must be supported on all collections identified in the value of
  * a DAV:principal-collection-set property.
- * <p/>
+ * <p>
  * The request body MUST be an empty DAV:principal-search-property-set element.
- * <p/>
+ * <p>
  * The response body MUSt be a DAV:principal-search-property-set XML element
  * with the following structure:
  * <pre>
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/server/AbstractWebdavServlet.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/server/AbstractWebdavServlet.java
index 128946e..5f402b3 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/server/AbstractWebdavServlet.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/server/AbstractWebdavServlet.java
@@ -92,7 +92,7 @@ import java.util.List;
 
 /**
  * <code>AbstractWebdavServlet</code>
- * <p/>
+ * <p>
  */
 abstract public class AbstractWebdavServlet extends HttpServlet implements DavConstants {
 
@@ -134,6 +134,13 @@ abstract public class AbstractWebdavServlet extends HttpServlet implements DavCo
      */
     public static final String INIT_PARAM_CSRF_PROTECTION = "csrf-protection";
 
+    /** 
+     * Name of the 'createAbsoluteURI' init parameter that defines whether hrefs
+     * should be created with a absolute URI or as absolute Path (ContextPath). 
+     * The value should be 'true' or 'false'. The default value if not set is true.
+     */
+    public final static String INIT_PARAM_CREATE_ABSOLUTE_URI = "createAbsoluteURI";
+
 
     /**
      * Header value as specified in the {@link #INIT_PARAM_AUTHENTICATE_HEADER} parameter.
@@ -145,6 +152,11 @@ abstract public class AbstractWebdavServlet extends HttpServlet implements DavCo
      */
     private CSRFUtil csrfUtil;
 
+    /**
+     * Create per default absolute URI hrefs
+     */
+    private boolean createAbsoluteURI = true;
+
     @Override
     public void init() throws ServletException {
         super.init();
@@ -160,6 +172,13 @@ abstract public class AbstractWebdavServlet extends HttpServlet implements DavCo
         String csrfParam = getInitParameter(INIT_PARAM_CSRF_PROTECTION);
         csrfUtil = new CSRFUtil(csrfParam);
         log.info(INIT_PARAM_CSRF_PROTECTION + " = " + csrfParam);
+
+        //create absolute URI hrefs..
+        String param = getInitParameter(INIT_PARAM_CREATE_ABSOLUTE_URI);
+        if (param != null) {
+            createAbsoluteURI = Boolean.parseBoolean(param);
+        }
+        log.info(INIT_PARAM_CREATE_ABSOLUTE_URI + " = " + createAbsoluteURI);
     }
 
     /**
@@ -226,6 +245,15 @@ abstract public class AbstractWebdavServlet extends HttpServlet implements DavCo
     }
 
     /**
+     * Returns if a absolute URI should be created for hrefs.
+     * 
+     * @return absolute URI hrefs
+     */
+    protected boolean isCreateAbsoluteURI() {
+        return createAbsoluteURI;
+    }
+    
+    /**
      * Service the given request.
      *
      * @param request
@@ -237,7 +265,7 @@ abstract public class AbstractWebdavServlet extends HttpServlet implements DavCo
     protected void service(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
 
-        WebdavRequest webdavRequest = new WebdavRequestImpl(request, getLocatorFactory());
+        WebdavRequest webdavRequest = new WebdavRequestImpl(request, getLocatorFactory(), isCreateAbsoluteURI());
         // DeltaV requires 'Cache-Control' header for all methods except 'VERSION-CONTROL' and 'REPORT'.
         int methodCode = DavMethods.getMethodCode(request.getMethod());
         boolean noCache = DavMethods.isDeltaVMethod(webdavRequest) && !(DavMethods.DAV_VERSION_CONTROL == methodCode || DavMethods.DAV_REPORT == methodCode);
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/EncodeUtil.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/EncodeUtil.java
index 204a78d..bf0b5cf 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/EncodeUtil.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/EncodeUtil.java
@@ -45,7 +45,7 @@ public final class EncodeUtil {
      * The list of characters that are not encoded by the <code>escape()</code>
      * and <code>unescape()</code> METHODS. They contains the characters as
      * defined 'unreserved' in section 2.3 of the RFC 2396 'URI generic syntax':
-     * <p/>
+     * <p>
      * <pre>
      * unreserved  = alphanum | mark
      * mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/LinkHeaderFieldParser.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/LinkHeaderFieldParser.java
index c0e4b4e..5381e3b 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/LinkHeaderFieldParser.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/LinkHeaderFieldParser.java
@@ -1,199 +1,199 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.webdav.util;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.apache.commons.httpclient.NameValuePair;
-import org.apache.commons.httpclient.util.ParameterParser;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Simple parser for HTTP Link header fields, as defined in RFC 5988.
- */
-public class LinkHeaderFieldParser {
-
-    /**
-     * the default logger
-     */
-    private static Logger log = LoggerFactory.getLogger(LinkHeaderFieldParser.class);
-
-    private final List<LinkRelation> relations;
-
-    public LinkHeaderFieldParser(List<String> fieldValues) {
-        List<LinkRelation> tmp = new ArrayList<LinkRelation>();
-        if (fieldValues != null) {
-            for (String value : fieldValues) {
-                addFields(tmp, value);
-            }
-        }
-        relations = Collections.unmodifiableList(tmp);
-    }
-
-    public LinkHeaderFieldParser(Enumeration<?> en) {
-        if (en != null && en.hasMoreElements()) {
-            List<LinkRelation> tmp = new ArrayList<LinkRelation>();
-
-            while (en.hasMoreElements()) {
-                addFields(tmp, en.nextElement().toString());
-            }
-            relations = Collections.unmodifiableList(tmp);
-        } else {
-            // optimize case of no Link headers
-            relations = Collections.emptyList();
-        }
-    }
-
-    public String getFirstTargetForRelation(String relationType) {
-
-        for (LinkRelation lr : relations) {
-
-            String relationNames = lr.getParameters().get("rel");
-            if (relationNames != null) {
-
-                // split rel value on whitespace
-                for (String rn : relationNames.toLowerCase(Locale.ENGLISH)
-                        .split("\\s")) {
-                    if (relationType.equals(rn)) {
-                        return lr.getTarget();
-                    }
-                }
-            }
-        }
-
-        return null;
-    }
-
-    // A single header field instance can contain multiple, comma-separated
-    // fields.
-    private void addFields(List<LinkRelation> l, String fieldValue) {
-
-        boolean insideAngleBrackets = false;
-        boolean insideDoubleQuotes = false;
-
-        for (int i = 0; i < fieldValue.length(); i++) {
-
-            char c = fieldValue.charAt(i);
-
-            if (insideAngleBrackets) {
-                insideAngleBrackets = c != '>';
-            } else if (insideDoubleQuotes) {
-                insideDoubleQuotes = c != '"';
-                if (c == '\\' && i < fieldValue.length() - 1) {
-                    // skip over next character
-                    c = fieldValue.charAt(++i);
-                }
-            } else {
-                insideAngleBrackets = c == '<';
-                insideDoubleQuotes = c == '"';
-
-                if (c == ',') {
-                    String v = fieldValue.substring(0, i);
-                    if (v.length() > 0) {
-                        try {
-                            l.add(new LinkRelation(v));
-                        } catch (Exception ex) {
-                            log.warn("parse error in Link Header field value",
-                                    ex);
-                        }
-                    }
-                    addFields(l, fieldValue.substring(i + 1));
-                    return;
-                }
-            }
-        }
-
-        if (fieldValue.length() > 0) {
-            try {
-                l.add(new LinkRelation(fieldValue));
-            } catch (Exception ex) {
-                log.warn("parse error in Link Header field value", ex);
-            }
-        }
-    }
-
-    private static class LinkRelation {
-
-        private static Pattern P = Pattern.compile("\\s*<(.*)>\\s*(.*)");
-
-        private String target;
-        private Map<String, String> parameters;
-
-        /**
-         * Parses a single link relation, consisting of <URI> and optional
-         * parameters.
-         * 
-         * @param field
-         *            field value
-         * @throws Exception
-         */
-        @SuppressWarnings("unchecked")
-        public LinkRelation(String field) throws Exception {
-
-            // find the link target using a regexp
-            Matcher m = P.matcher(field);
-            if (!m.matches()) {
-                throw new Exception("illegal Link header field value:" + field);
-            }
-
-            target = m.group(1);
-
-            // pass the remainder to the generic parameter parser
-            List<NameValuePair> params = (List<NameValuePair>) new ParameterParser()
-                    .parse(m.group(2), ';');
-
-            if (params.size() == 0) {
-                parameters = Collections.emptyMap();
-            } else if (params.size() == 1) {
-                NameValuePair nvp = params.get(0);
-                parameters = Collections.singletonMap(nvp.getName()
-                        .toLowerCase(Locale.ENGLISH), nvp.getValue());
-            } else {
-                parameters = new HashMap<String, String>();
-                for (NameValuePair p : params) {
-                    if (null != parameters.put(
-                            p.getName().toLowerCase(Locale.ENGLISH),
-                            p.getValue())) {
-                        throw new Exception("duplicate parameter + "
-                                + p.getName() + " field ignored");
-                    }
-                }
-            }
-        }
-
-        public String getTarget() {
-            return target;
-        }
-
-        public Map<String, String> getParameters() {
-            return parameters;
-        }
-
-        public String toString() {
-            return target + " " + parameters;
-        }
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.httpclient.util.ParameterParser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Simple parser for HTTP Link header fields, as defined in RFC 5988.
+ */
+public class LinkHeaderFieldParser {
+
+    /**
+     * the default logger
+     */
+    private static Logger log = LoggerFactory.getLogger(LinkHeaderFieldParser.class);
+
+    private final List<LinkRelation> relations;
+
+    public LinkHeaderFieldParser(List<String> fieldValues) {
+        List<LinkRelation> tmp = new ArrayList<LinkRelation>();
+        if (fieldValues != null) {
+            for (String value : fieldValues) {
+                addFields(tmp, value);
+            }
+        }
+        relations = Collections.unmodifiableList(tmp);
+    }
+
+    public LinkHeaderFieldParser(Enumeration<?> en) {
+        if (en != null && en.hasMoreElements()) {
+            List<LinkRelation> tmp = new ArrayList<LinkRelation>();
+
+            while (en.hasMoreElements()) {
+                addFields(tmp, en.nextElement().toString());
+            }
+            relations = Collections.unmodifiableList(tmp);
+        } else {
+            // optimize case of no Link headers
+            relations = Collections.emptyList();
+        }
+    }
+
+    public String getFirstTargetForRelation(String relationType) {
+
+        for (LinkRelation lr : relations) {
+
+            String relationNames = lr.getParameters().get("rel");
+            if (relationNames != null) {
+
+                // split rel value on whitespace
+                for (String rn : relationNames.toLowerCase(Locale.ENGLISH)
+                        .split("\\s")) {
+                    if (relationType.equals(rn)) {
+                        return lr.getTarget();
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    // A single header field instance can contain multiple, comma-separated
+    // fields.
+    private void addFields(List<LinkRelation> l, String fieldValue) {
+
+        boolean insideAngleBrackets = false;
+        boolean insideDoubleQuotes = false;
+
+        for (int i = 0; i < fieldValue.length(); i++) {
+
+            char c = fieldValue.charAt(i);
+
+            if (insideAngleBrackets) {
+                insideAngleBrackets = c != '>';
+            } else if (insideDoubleQuotes) {
+                insideDoubleQuotes = c != '"';
+                if (c == '\\' && i < fieldValue.length() - 1) {
+                    // skip over next character
+                    c = fieldValue.charAt(++i);
+                }
+            } else {
+                insideAngleBrackets = c == '<';
+                insideDoubleQuotes = c == '"';
+
+                if (c == ',') {
+                    String v = fieldValue.substring(0, i);
+                    if (v.length() > 0) {
+                        try {
+                            l.add(new LinkRelation(v));
+                        } catch (Exception ex) {
+                            log.warn("parse error in Link Header field value",
+                                    ex);
+                        }
+                    }
+                    addFields(l, fieldValue.substring(i + 1));
+                    return;
+                }
+            }
+        }
+
+        if (fieldValue.length() > 0) {
+            try {
+                l.add(new LinkRelation(fieldValue));
+            } catch (Exception ex) {
+                log.warn("parse error in Link Header field value", ex);
+            }
+        }
+    }
+
+    private static class LinkRelation {
+
+        private static Pattern P = Pattern.compile("\\s*<(.*)>\\s*(.*)");
+
+        private String target;
+        private Map<String, String> parameters;
+
+        /**
+         * Parses a single link relation, consisting of <URI> and optional
+         * parameters.
+         * 
+         * @param field
+         *            field value
+         * @throws Exception
+         */
+        @SuppressWarnings("unchecked")
+        public LinkRelation(String field) throws Exception {
+
+            // find the link target using a regexp
+            Matcher m = P.matcher(field);
+            if (!m.matches()) {
+                throw new Exception("illegal Link header field value:" + field);
+            }
+
+            target = m.group(1);
+
+            // pass the remainder to the generic parameter parser
+            List<NameValuePair> params = (List<NameValuePair>) new ParameterParser()
+                    .parse(m.group(2), ';');
+
+            if (params.size() == 0) {
+                parameters = Collections.emptyMap();
+            } else if (params.size() == 1) {
+                NameValuePair nvp = params.get(0);
+                parameters = Collections.singletonMap(nvp.getName()
+                        .toLowerCase(Locale.ENGLISH), nvp.getValue());
+            } else {
+                parameters = new HashMap<String, String>();
+                for (NameValuePair p : params) {
+                    if (null != parameters.put(
+                            p.getName().toLowerCase(Locale.ENGLISH),
+                            p.getValue())) {
+                        throw new Exception("duplicate parameter + "
+                                + p.getName() + " field ignored");
+                    }
+                }
+            }
+        }
+
+        public String getTarget() {
+            return target;
+        }
+
+        public Map<String, String> getParameters() {
+            return parameters;
+        }
+
+        public String toString() {
+            return target + " " + parameters;
+        }
+    }
+}
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/package-info.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/package-info.java
index 1e7680e..d3a9da7 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/package-info.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/util/package-info.java
@@ -14,5 +14,5 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- at aQute.bnd.annotation.Version("1.0.0")
+ at aQute.bnd.annotation.Version("1.0.1")
 package org.apache.jackrabbit.webdav.util;
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/ActivityResource.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/ActivityResource.java
index f0921b6..6d20aa6 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/ActivityResource.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/ActivityResource.java
@@ -24,7 +24,7 @@ import org.apache.jackrabbit.webdav.property.DavPropertyName;
  * by successor relationships. If an activity selects versions from multiple
  * version histories, the versions selected in each version history must be on a
  * single line of descent.
- * <p/>
+ * <p>
  * RFC 3253 defines the following required live properties for an Activity
  * resource.
  * <ul>
@@ -37,11 +37,11 @@ import org.apache.jackrabbit.webdav.property.DavPropertyName;
  * property returned by an Activity resource must be
  * {@link org.apache.jackrabbit.webdav.property.ResourceType#ACTIVITY DAV:activity}</li>
  * </ul>
- * <p/>
+ * <p>
  * The Activity resource must support all methods defined for a
  * {@link DeltaVResource DeltaV-compliant resource}. Since no additional methods
  * are required for an activity this interface mainly acts as marker.
- * <p/>
+ * <p>
  * Please refer to <a href="http://www.ietf.org/rfc/rfc3253.txt">RFC 3253</a>
  * Section 13 for a complete description of this resource type.
  */
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/BaselineResource.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/BaselineResource.java
index d690eaf..b48d6f2 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/BaselineResource.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/BaselineResource.java
@@ -26,7 +26,7 @@ import org.apache.jackrabbit.webdav.DavException;
  * Such as new versions are created by CHECKIN of a version-controlled
  * resource, a new baseline is created, whenever the VCC resource, that
  * represents a set of resources rather than a single resource, is checked-in.
- * <p/>
+ * <p>
  * Since the baseline behaves like a <code>VersionResource</code> and only is
  * defined to provide additional protected properties, this interface only adds
  * a convenience method that allows to retrieve the baseline collection.
@@ -52,7 +52,7 @@ public interface BaselineResource extends VersionResource {
      * version of the corresponding vc-configuration-resource). In other words:
      * each member in the list must correspond to a member of the baseline-controlled
      * collection at the time this baseline (version) was created.
-     * <p/>
+     * <p>
      *
      * Note that the DAV:baseline-collection represents a
      * {@link org.apache.jackrabbit.webdav.property.HrefProperty HrefProperty}
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/DeltaVConstants.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/DeltaVConstants.java
index 9730a79..83cc7bd 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/DeltaVConstants.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/DeltaVConstants.java
@@ -141,7 +141,7 @@ public interface DeltaVConstants {
      * Protected "workspace" property indicating the workspace of a resource.
      * This property is required for all resources if (but only if) the workspace
      * feature is supported.
-     * <p/>
+     * <p>
      * Note that the DAV:activity-version-set represents a
      * {@link org.apache.jackrabbit.webdav.property.HrefProperty HrefProperty}.
      * It is defined to have the following format:
@@ -158,7 +158,7 @@ public interface DeltaVConstants {
      * property for all resources that are member of a version-controlled
      * configuration. This may be the case if the resource is a collection under
      * baseline control or is a member of a collection under baseline control.
-     * <p/>
+     * <p>
      * Note that the DAV:activity-version-set represents a
      * {@link org.apache.jackrabbit.webdav.property.HrefProperty HrefProperty}.
      * It is defined to have the following format:
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/DeltaVResource.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/DeltaVResource.java
index de7bd67..a8a909d 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/DeltaVResource.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/DeltaVResource.java
@@ -25,7 +25,7 @@ import org.apache.jackrabbit.webdav.version.report.ReportInfo;
 /**
  * The <code>DeltaVResource</code> encapsulates the functionality common to all
  * DeltaV compliant resources.
- * <p/>
+ * <p>
  * RFC 3253 defines the following required properties:
  * <ul>
  * <li>{@link DeltaVConstants#COMMENT DAV:comment}</li>
@@ -35,7 +35,7 @@ import org.apache.jackrabbit.webdav.version.report.ReportInfo;
  * <li>{@link DeltaVConstants#SUPPORTED_REPORT_SET DAV:supported-report-set}</li>
  * <li>all properties defined in WebDAV [RFC2518].</li>
  * </ul>
- * <p/>
+ * <p>
  * In addition a DeltaV compliant resource must support the following METHODS:
  * <ul>
  * <li>REPORT</li>
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/VersionControlledResource.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/VersionControlledResource.java
index 341e235..c8d6e3b 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/VersionControlledResource.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/VersionControlledResource.java
@@ -25,7 +25,7 @@ import org.apache.jackrabbit.webdav.property.DavPropertyName;
  * <code>VersionableResource</code> a resource, that has already been put
  * under version-control. This resource can be checked-in, checked-out and
  * has its own {@link VersionHistoryResource version history}.
- * <p/>
+ * <p>
  * RFC 3253 defines the following required properties for a
  * version-controlled resource (vc-resource):
  * <ul>
@@ -67,7 +67,7 @@ import org.apache.jackrabbit.webdav.property.DavPropertyName;
  * <li>DAV:subbaseline-set (if the configuration resource is checked-out)</li>
  * </ul>
  *
- * <p/>
+ * <p>
  * In addition a version-controlled resource must support the following METHODS:
  * <ul>
  * <li>VERSION-CONTROL</li>
@@ -224,7 +224,7 @@ public interface VersionControlledResource extends VersionableResource {
      * to merge into that checked-out resource the latest version selected by
      * that activity from that version history, and then modify the
      * DAV:predecessor-set of that checked-out resource to identify that version.
-     * <p/>
+     * <p>
      * This property is defined to have the following format:
      * <pre>
      * <!ELEMENT unreserved (#PCDATA)>
@@ -249,7 +249,7 @@ public interface VersionControlledResource extends VersionableResource {
      * DAV:eclipsed-set property present on a collection identifies all
      * internal members that are not version-controlled and hide a vc internal
      * member with the same name.
-     * <p/>
+     * <p>
      * This property is defined to have the following format:
      * <pre>
      * <!ELEMENT eclipsed-set (binding-name*)>
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/VersionHistoryResource.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/VersionHistoryResource.java
index 968e21e..307ed7f 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/VersionHistoryResource.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/VersionHistoryResource.java
@@ -23,14 +23,14 @@ import org.apache.jackrabbit.webdav.property.ResourceType;
 /**
  * <code>VersionHistoryResource</code> represents a collection that has
  * all versions of a given version-controlled resource as members.
- * <p/>
+ * <p>
  * RFC 3253 defines the following required properties for a version history:
  * <ul>
  * <li>DAV:version-set</li>
  * <li>DAV:root-version</li>
  * <li>all DeltaV-compliant resource properties.</li>
  * </ul>
- * <p/>
+ * <p>
  * In addition a version history resource must support the following METHODS:
  * <ul>
  * <li>all DeltaV-compliant resource METHODS.</li>
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/VersionResource.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/VersionResource.java
index ae274db..a42e929 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/VersionResource.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/VersionResource.java
@@ -26,7 +26,7 @@ import org.apache.jackrabbit.webdav.property.DavPropertyName;
  * a distinct new URL for each new version, and this URL will never be used to
  * identify any resource other than that version. The content and dead properties
  * of a version never change.
- * <p/>
+ * <p>
  * RFC 3253 defines the following required properties for a version resource:
  * <ul>
  * <li>DAV:predecessor-set  (protected)</li>
@@ -48,7 +48,7 @@ import org.apache.jackrabbit.webdav.property.DavPropertyName;
  * <li>DAV:version-controlled-binding-set</li>
  * </ul>
  *
- * <p/>
+ * <p>
  * In addition a version resource must support the following METHODS:
  * <ul>
  * <li>LABEL (label)</li>
@@ -157,7 +157,7 @@ public interface VersionResource extends DeltaVResource {
      * A server MAY restrict the DAV:activity-set to identify a single activity.
      * A server MAY refuse to allow the value of the DAV:activity-set property
      * of a version to be modified.
-     * <p/>
+     * <p>
      * The property is defined to have the following format:
      * <pre>
      * <!ELEMENT activity-set (href*)>
@@ -172,7 +172,7 @@ public interface VersionResource extends DeltaVResource {
      * DAV:version-controlled-binding-set property identifies the name and the
      * version history of all version-controlled internal members of the
      * collection this version resource belongs to.
-     * <p/>
+     * <p>
      * This property is defined to have the following format:
      * <pre>
      * <!ELEMENT version-controlled-binding-set (version-controlled-binding*)>
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/VersionableResource.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/VersionableResource.java
index 5e15f41..f057923 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/VersionableResource.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/VersionableResource.java
@@ -23,14 +23,14 @@ import org.apache.jackrabbit.webdav.DavException;
  * {@link DeltaVResource}, that allows to adding version-control support. By
  * calling {@link #addVersionControl()} resource is put under version control,
  * thus the versionable resource turns into a version controlled resource.
- * <p/>
+ * <p>
  * RFC 3253 defines the following required properties for a versionable resource:
  * <ul>
  * <li>{@link DeltaVConstants#WORKSPACE DAV:workspace} (workspace feature)</li>
  * <li>DAV:version-controlled-configuration (baseline feature)</li>
  * <li>all DeltaV-compliant resource properties</li>
  * </ul>
- * <p/>
+ * <p>
  * In addition a versionable resource must support the following METHODS:
  * <ul>
  * <li>VERSION-CONTROL</li>
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/WorkspaceResource.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/WorkspaceResource.java
index a2f2732..19273c2 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/WorkspaceResource.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/WorkspaceResource.java
@@ -22,7 +22,7 @@ import org.apache.jackrabbit.webdav.property.DavPropertyName;
  * A workspace resource is a collection whose members are related
  * version-controlled and non-version-controlled resources.
  *
- * <p/>
+ * <p>
  * RFC 3253 defines the following required live properties for an Workspace
  * resource.
  * <ul>
@@ -36,7 +36,7 @@ import org.apache.jackrabbit.webdav.property.DavPropertyName;
  * The workspace resource must support all methods defined for a DeltaV-compliant
  * collection. Since no additional methods are required for a workspace this
  * interface mainly acts as marker.
- * <p/>
+ * <p>
  * Please refer to <a href="http://www.ietf.org/rfc/rfc3253.txt">RFC 3253</a>
  * Section 6 for a complete description of this resource type.
  */
@@ -67,7 +67,7 @@ public interface WorkspaceResource extends DeltaVResource {
      * be used. This allows an activity-unaware client to update a workspace in
      * which activity tracking is required. The DAV:current-activity-set MAY be
      * restricted to identify at most one activity.
-     * <p/>
+     * <p>
      * The property is defined to have the following format:
      * <pre>
      * <!ELEMENT current-activity-set (href*)>
@@ -82,7 +82,7 @@ public interface WorkspaceResource extends DeltaVResource {
      * for a workspace resource: DAV:baseline-controlled-collection-set lists
      * all collections of this workspace, that are under baseline control. This
      * list may include the workspace itself.
-     * <p/>
+     * <p>
      * The property is defined to have the following format:
      * <pre>
      * <!ELEMENT baseline-controlled-collection-set (href*)>
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/report/ExpandPropertyReport.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/report/ExpandPropertyReport.java
index 586d11f..130c673 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/report/ExpandPropertyReport.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/version/report/ExpandPropertyReport.java
@@ -45,7 +45,7 @@ import java.util.List;
  * that provides a mechanism for retrieving in one request the properties from
  * the resources identified by those DAV:href elements. It should be supported by
  * all resources that support the REPORT method.
- * <p/>
+ * <p>
  * RFC 3253 specifies the following required format for the request body:
  * <pre>
  * <!ELEMENT expand-property (property*)>
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DavDocumentBuilderFactory.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DavDocumentBuilderFactory.java
new file mode 100644
index 0000000..2807657
--- /dev/null
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DavDocumentBuilderFactory.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.xml;
+
+import java.io.IOException;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Custom {@link DocumentBuilderFactory} extended for use in WebDAV.
+ */
+public class DavDocumentBuilderFactory {
+
+    private static final Logger LOG = LoggerFactory.getLogger(DomUtil.class);
+
+    private final DocumentBuilderFactory DEFAULT_FACTORY = createFactory();
+
+    private DocumentBuilderFactory BUILDER_FACTORY = DEFAULT_FACTORY;
+
+    private DocumentBuilderFactory createFactory() {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setNamespaceAware(true);
+        factory.setIgnoringComments(true);
+        factory.setIgnoringElementContentWhitespace(true);
+        factory.setCoalescing(true);
+        try {
+            factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+        } catch (ParserConfigurationException e) {
+            LOG.warn("Secure XML processing is not supported", e);
+        } catch (AbstractMethodError e) {
+            LOG.warn("Secure XML processing is not supported", e);
+        }
+        return factory;
+    }
+
+    public void setFactory(DocumentBuilderFactory documentBuilderFactory) {
+        LOG.debug("DocumentBuilderFactory changed to: " + documentBuilderFactory);
+        BUILDER_FACTORY = documentBuilderFactory != null ? documentBuilderFactory : DEFAULT_FACTORY;
+    }
+
+    /**
+     * An entity resolver that does not allow external entity resolution. See
+     * RFC 4918, Section 20.6
+     */
+    private static final EntityResolver DEFAULT_ENTITY_RESOLVER = new EntityResolver() {
+        @Override
+        public InputSource resolveEntity(String publicId, String systemId) throws IOException {
+            LOG.debug("Resolution of external entities in XML payload not supported - publicId: " + publicId + ", systemId: "
+                    + systemId);
+            throw new IOException("This parser does not support resolution of external entities (publicId: " + publicId
+                    + ", systemId: " + systemId + ")");
+        }
+    };
+
+    public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
+        DocumentBuilder db = BUILDER_FACTORY.newDocumentBuilder();
+        if (BUILDER_FACTORY == DEFAULT_FACTORY) {
+            // if this is the default factory: set the default entity resolver as well
+            db.setEntityResolver(DEFAULT_ENTITY_RESOLVER);
+        }
+        db.setErrorHandler(new DefaultHandler());
+        return db;
+    }
+}
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java
index 70508cc..bd6bc60 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java
@@ -28,9 +28,7 @@ import org.w3c.dom.NodeList;
 import org.w3c.dom.Text;
 import org.w3c.dom.NamedNodeMap;
 import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
 
-import javax.xml.XMLConstants;
 import javax.xml.namespace.QName;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -56,26 +54,10 @@ public class DomUtil {
     private static Logger log = LoggerFactory.getLogger(DomUtil.class);
 
     /**
-     * Constant for <code>DocumentBuilderFactory</code> which is used
+     * Constant for <code>DavDocumentBuilderFactory</code> which is used
      * to create and parse DOM documents.
      */
-    private static DocumentBuilderFactory BUILDER_FACTORY = createFactory();
-
-    private static DocumentBuilderFactory createFactory() {
-        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-        factory.setNamespaceAware(true);
-        factory.setIgnoringComments(true);
-        factory.setIgnoringElementContentWhitespace(true);
-        factory.setCoalescing(true);
-        try {
-            factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
-        } catch (ParserConfigurationException e) {
-            log.warn("Secure XML processing is not supported", e);
-        } catch (AbstractMethodError e) {
-            log.warn("Secure XML processing is not supported", e);
-        }
-        return factory;
-    }
+    private static final DavDocumentBuilderFactory BUILDER_FACTORY = new DavDocumentBuilderFactory();
 
     /**
      * Support the replacement of {@link #BUILDER_FACTORY}. This is useful
@@ -88,7 +70,7 @@ public class DomUtil {
      */
     public static void setBuilderFactory(
             DocumentBuilderFactory documentBuilderFactory) {
-        BUILDER_FACTORY = documentBuilderFactory;
+        BUILDER_FACTORY.setFactory(documentBuilderFactory);
     }
 
     /**
@@ -119,11 +101,6 @@ public class DomUtil {
     public static Document parseDocument(InputStream stream)
             throws ParserConfigurationException, SAXException, IOException {
         DocumentBuilder docBuilder = BUILDER_FACTORY.newDocumentBuilder();
-
-        // Set an error handler to prevent parsers from printing error messages
-        // to standard output!
-        docBuilder.setErrorHandler(new DefaultHandler());
-
         return docBuilder.parse(stream);
     }
 
@@ -253,6 +230,22 @@ public class DomUtil {
     }
 
     /**
+     * Calls {@link #getTextTrim(Element)} on the first child element that matches
+     * the given name.
+     *
+     * @param parent
+     * @param childName
+     * @return text contained in the first child that matches the given name
+     * or <code>null</code>. Note, that leading and trailing whitespace
+     * is removed from the text.
+     * @see #getTextTrim(Element)
+     */
+    public static String getChildTextTrim(Element parent, QName childName) {
+        Element child = getChildElement(parent, childName);
+        return (child == null) ? null : getTextTrim(child);
+    }
+
+    /**
      * Returns true if the given parent node has a child element that matches
      * the specified local name and namespace.
      *
@@ -669,17 +662,14 @@ public class DomUtil {
     /**
      * Converts the given timeout (long value defining the number of milli-
      * second until timeout is reached) to its Xml representation as defined
-     * by RTF 2518.<br>
-     * Note, that {@link DavConstants#INFINITE_TIMEOUT} is not represented by the String
-     * {@link DavConstants#TIMEOUT_INFINITE 'Infinite'} defined by RFC 2518, due to a known
-     * issue with Microsoft Office that opens the document "read only" and
-     * never unlocks the resource if the timeout is missing or 'Infinite'.
+     * by RFC 4918.<br>
      *
      * @param timeout number of milli-seconds until timeout is reached.
      * @return 'timeout' Xml element
      */
     public static Element timeoutToXml(long timeout, Document factory) {
-        String expString = "Second-"+ timeout/1000;
+        boolean infinite = timeout / 1000 > Integer.MAX_VALUE || timeout == DavConstants.INFINITE_TIMEOUT;
+        String expString = infinite ? DavConstants.TIMEOUT_INFINITE : "Second-" + timeout / 1000;
         return createElement(factory, DavConstants.XML_TIMEOUT, DavConstants.NAMESPACE, expString);
     }
 
diff --git a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/package-info.java b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/package-info.java
index 2778178..8a587ec 100644
--- a/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/package-info.java
+++ b/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/package-info.java
@@ -14,5 +14,5 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- at aQute.bnd.annotation.Version("1.0.0")
+ at aQute.bnd.annotation.Version("1.1.0")
 package org.apache.jackrabbit.webdav.xml;
diff --git a/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/server/RFC4918DestinationHeaderTest.java b/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/server/RFC4918DestinationHeaderTest.java
index a06ae5a..4733868 100644
--- a/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/server/RFC4918DestinationHeaderTest.java
+++ b/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/server/RFC4918DestinationHeaderTest.java
@@ -1,120 +1,120 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.webdav.server;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-
-import junit.framework.TestCase;
-
-import org.apache.commons.httpclient.HttpClient;
-import org.apache.commons.httpclient.HttpException;
-import org.apache.commons.httpclient.UsernamePasswordCredentials;
-import org.apache.commons.httpclient.auth.AuthScope;
-import org.apache.commons.httpclient.methods.HeadMethod;
-import org.apache.commons.httpclient.methods.PutMethod;
-import org.apache.jackrabbit.webdav.DavException;
-import org.apache.jackrabbit.webdav.client.methods.DeleteMethod;
-import org.apache.jackrabbit.webdav.client.methods.MoveMethod;
-
-/**
- * Test cases for RFC 4918 Destination header functionality
- * (see <a href="http://www.webdav.org/specs/rfc4918.html#rfc.section.10.3">RFC 4918, Section 10.3</a>
- * <p>
- * Required system properties:
- * <ul>
- *   <li>webdav.test.url</li>
- *   <li>webdav.test.username</li>
- *   <li>webdav.test.password</li>
- * </ul>
- */
-
-public class RFC4918DestinationHeaderTest extends TestCase {
-
-    private String root;
-    private URI uri;
-    private String username, password;
-    private HttpClient client;
-    
-    @Override
-    protected void setUp() throws Exception {
-        this.uri = URI.create(System.getProperty("webdav.test.url"));
-        this.root = this.uri.toASCIIString();
-        if (!this.root.endsWith("/")) {
-            this.root += "/";
-        }
-        this.username = System.getProperty(("webdav.test.username"), "");
-        this.password = System.getProperty(("webdav.test.password"), "");
-        this.client = new HttpClient();
-        this.client.getState().setCredentials(
-                new AuthScope(this.uri.getHost(), this.uri.getPort()),
-                new UsernamePasswordCredentials(this.username, this.password));
-        super.setUp();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-    }
-    
-    public void testMove() throws HttpException, IOException, DavException, URISyntaxException {
-
-        String testuri = this.root + "movetest";
-        String destinationuri = testuri + "2";
-        String destinationpath = new URI(destinationuri).getRawPath();
-        // make sure the scheme is removed
-        assertFalse(destinationpath.contains(":"));
-        
-        int status;
-        try {
-            PutMethod put = new PutMethod(testuri);
-            status = this.client.executeMethod(put);
-            assertTrue("status: " + status, status == 200 || status == 201 || status == 204);
-
-            // try to move outside the servlet's name space
-            MoveMethod move = new MoveMethod(testuri, "/foobar", true);
-            status = this.client.executeMethod(move);
-            assertTrue("status: " + status, status == 403);
-
-            // try a relative path
-            move = new MoveMethod(testuri, "foobar", true);
-            status = this.client.executeMethod(move);
-            assertTrue("status: " + status, status == 400);
-
-            move = new MoveMethod(testuri, destinationpath, true);
-            status = this.client.executeMethod(move);
-            assertTrue("status: " + status, status == 200 || status == 201 || status == 204);
-            
-            HeadMethod head = new HeadMethod(destinationuri);
-            status = this.client.executeMethod(head);
-            assertTrue("status: " + status, status == 200);
-
-            head = new HeadMethod(testuri);
-            status = this.client.executeMethod(head);
-            assertTrue("status: " + status, status == 404);
-
-        } finally {
-            DeleteMethod delete = new DeleteMethod(testuri);
-            status = this.client.executeMethod(delete);
-            assertTrue("status: " + status, status == 200 || status == 204 || status == 404);
-            delete = new DeleteMethod(destinationuri);
-            status = this.client.executeMethod(delete);
-            assertTrue("status: " + status, status == 200 || status == 204 || status == 404);
-        }
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.server;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpException;
+import org.apache.commons.httpclient.UsernamePasswordCredentials;
+import org.apache.commons.httpclient.auth.AuthScope;
+import org.apache.commons.httpclient.methods.HeadMethod;
+import org.apache.commons.httpclient.methods.PutMethod;
+import org.apache.jackrabbit.webdav.DavException;
+import org.apache.jackrabbit.webdav.client.methods.DeleteMethod;
+import org.apache.jackrabbit.webdav.client.methods.MoveMethod;
+
+/**
+ * Test cases for RFC 4918 Destination header functionality
+ * (see <a href="http://www.webdav.org/specs/rfc4918.html#rfc.section.10.3">RFC 4918, Section 10.3</a>
+ * <p>
+ * Required system properties:
+ * <ul>
+ *   <li>webdav.test.url</li>
+ *   <li>webdav.test.username</li>
+ *   <li>webdav.test.password</li>
+ * </ul>
+ */
+
+public class RFC4918DestinationHeaderTest extends TestCase {
+
+    private String root;
+    private URI uri;
+    private String username, password;
+    private HttpClient client;
+    
+    @Override
+    protected void setUp() throws Exception {
+        this.uri = URI.create(System.getProperty("webdav.test.url"));
+        this.root = this.uri.toASCIIString();
+        if (!this.root.endsWith("/")) {
+            this.root += "/";
+        }
+        this.username = System.getProperty(("webdav.test.username"), "");
+        this.password = System.getProperty(("webdav.test.password"), "");
+        this.client = new HttpClient();
+        this.client.getState().setCredentials(
+                new AuthScope(this.uri.getHost(), this.uri.getPort()),
+                new UsernamePasswordCredentials(this.username, this.password));
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+    
+    public void testMove() throws HttpException, IOException, DavException, URISyntaxException {
+
+        String testuri = this.root + "movetest";
+        String destinationuri = testuri + "2";
+        String destinationpath = new URI(destinationuri).getRawPath();
+        // make sure the scheme is removed
+        assertFalse(destinationpath.contains(":"));
+        
+        int status;
+        try {
+            PutMethod put = new PutMethod(testuri);
+            status = this.client.executeMethod(put);
+            assertTrue("status: " + status, status == 200 || status == 201 || status == 204);
+
+            // try to move outside the servlet's name space
+            MoveMethod move = new MoveMethod(testuri, "/foobar", true);
+            status = this.client.executeMethod(move);
+            assertTrue("status: " + status, status == 403);
+
+            // try a relative path
+            move = new MoveMethod(testuri, "foobar", true);
+            status = this.client.executeMethod(move);
+            assertTrue("status: " + status, status == 400);
+
+            move = new MoveMethod(testuri, destinationpath, true);
+            status = this.client.executeMethod(move);
+            assertTrue("status: " + status, status == 200 || status == 201 || status == 204);
+            
+            HeadMethod head = new HeadMethod(destinationuri);
+            status = this.client.executeMethod(head);
+            assertTrue("status: " + status, status == 200);
+
+            head = new HeadMethod(testuri);
+            status = this.client.executeMethod(head);
+            assertTrue("status: " + status, status == 404);
+
+        } finally {
+            DeleteMethod delete = new DeleteMethod(testuri);
+            status = this.client.executeMethod(delete);
+            assertTrue("status: " + status, status == 200 || status == 204 || status == 404);
+            delete = new DeleteMethod(destinationuri);
+            status = this.client.executeMethod(delete);
+            assertTrue("status: " + status, status == 200 || status == 204 || status == 404);
+        }
+    }
+}
diff --git a/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/util/LinkHeaderFieldParserTest.java b/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/util/LinkHeaderFieldParserTest.java
index e52daaa..0170ac4 100644
--- a/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/util/LinkHeaderFieldParserTest.java
+++ b/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/util/LinkHeaderFieldParserTest.java
@@ -1,66 +1,66 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.jackrabbit.webdav.util;
-
-import java.util.Collections;
-
-import org.apache.jackrabbit.webdav.util.LinkHeaderFieldParser;
-
-import junit.framework.TestCase;
-
-/**
- * <code>LinkHeaderFieldParserTest</code>...
- */
-public class LinkHeaderFieldParserTest extends TestCase {
-
-    public void testSimple() {
-        LinkHeaderFieldParser lhfp = new LinkHeaderFieldParser(
-                Collections.singletonList("<a>; rel=foo"));
-        assertEquals("a", lhfp.getFirstTargetForRelation("foo"));
-    }
-
-    public void testMulti() {
-        LinkHeaderFieldParser lhfp = new LinkHeaderFieldParser(
-                Collections.singletonList("<a>; rel=foo, <b>; rel=bar"));
-        assertEquals("b", lhfp.getFirstTargetForRelation("bar"));
-    }
-
-    public void testMultiQs() {
-        LinkHeaderFieldParser lhfp = new LinkHeaderFieldParser(
-                Collections
-                        .singletonList("<a,>; rel=\"fo\\\"o,\", <b,>; rel=bar"));
-        assertEquals("b,", lhfp.getFirstTargetForRelation("bar"));
-    }
-
-    public void testTruncated() {
-        LinkHeaderFieldParser lhfp = new LinkHeaderFieldParser(
-                Collections.singletonList("<a,>; rel=\"x\\\""));
-        assertEquals("a,", lhfp.getFirstTargetForRelation("x\\"));
-    }
-
-    public void testCommas() {
-        LinkHeaderFieldParser lhfp = new LinkHeaderFieldParser(
-                Collections.singletonList(",<a>; rel=\"xy,z\","));
-        assertEquals("a", lhfp.getFirstTargetForRelation("xy,z"));
-    }
-
-    public void testMultiRel() {
-        LinkHeaderFieldParser lhfp = new LinkHeaderFieldParser(
-                Collections.singletonList(",<a>; rel=\"a b\""));
-        assertEquals("a", lhfp.getFirstTargetForRelation("a"));
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.util;
+
+import java.util.Collections;
+
+import org.apache.jackrabbit.webdav.util.LinkHeaderFieldParser;
+
+import junit.framework.TestCase;
+
+/**
+ * <code>LinkHeaderFieldParserTest</code>...
+ */
+public class LinkHeaderFieldParserTest extends TestCase {
+
+    public void testSimple() {
+        LinkHeaderFieldParser lhfp = new LinkHeaderFieldParser(
+                Collections.singletonList("<a>; rel=foo"));
+        assertEquals("a", lhfp.getFirstTargetForRelation("foo"));
+    }
+
+    public void testMulti() {
+        LinkHeaderFieldParser lhfp = new LinkHeaderFieldParser(
+                Collections.singletonList("<a>; rel=foo, <b>; rel=bar"));
+        assertEquals("b", lhfp.getFirstTargetForRelation("bar"));
+    }
+
+    public void testMultiQs() {
+        LinkHeaderFieldParser lhfp = new LinkHeaderFieldParser(
+                Collections
+                        .singletonList("<a,>; rel=\"fo\\\"o,\", <b,>; rel=bar"));
+        assertEquals("b,", lhfp.getFirstTargetForRelation("bar"));
+    }
+
+    public void testTruncated() {
+        LinkHeaderFieldParser lhfp = new LinkHeaderFieldParser(
+                Collections.singletonList("<a,>; rel=\"x\\\""));
+        assertEquals("a,", lhfp.getFirstTargetForRelation("x\\"));
+    }
+
+    public void testCommas() {
+        LinkHeaderFieldParser lhfp = new LinkHeaderFieldParser(
+                Collections.singletonList(",<a>; rel=\"xy,z\","));
+        assertEquals("a", lhfp.getFirstTargetForRelation("xy,z"));
+    }
+
+    public void testMultiRel() {
+        LinkHeaderFieldParser lhfp = new LinkHeaderFieldParser(
+                Collections.singletonList(",<a>; rel=\"a b\""));
+        assertEquals("a", lhfp.getFirstTargetForRelation("a"));
+    }
+}
diff --git a/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/ParserTest.java b/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/ParserTest.java
new file mode 100644
index 0000000..1d2861d
--- /dev/null
+++ b/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/ParserTest.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the \"License\"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an \"AS IS\" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.webdav.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import junit.framework.TestCase;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class ParserTest extends TestCase {
+
+    // see <http://en.wikipedia.org/wiki/Billion_laughs#Details>
+    public void testBillionLaughs() throws UnsupportedEncodingException {
+
+        String testBody = "<?xml version=\"1.0\"?>" + "<!DOCTYPE lolz [" + " <!ENTITY lol \"lol\">" + " <!ELEMENT lolz (#PCDATA)>"
+                + " <!ENTITY lol1 \"&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;\">"
+                + " <!ENTITY lol2 \"&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;\">"
+                + " <!ENTITY lol3 \"&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;\">"
+                + " <!ENTITY lol4 \"&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;\">"
+                + " <!ENTITY lol5 \"&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;\">"
+                + " <!ENTITY lol6 \"&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;\">"
+                + " <!ENTITY lol7 \"&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;\">"
+                + " <!ENTITY lol8 \"&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;\">"
+                + " <!ENTITY lol9 \"&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;\">" + "]>" + "<lolz>&lol9;</lolz>";
+        InputStream is = new ByteArrayInputStream(testBody.getBytes("UTF-8"));
+
+        try {
+            DomUtil.parseDocument(is);
+            fail("parsing this document should cause an exception");
+        } catch (Exception expected) {
+        }
+    }
+
+    public void testExternalEntities() throws IOException {
+
+        String dname = "target";
+        String fname = "test.xml";
+
+        File f = new File(dname, fname);
+        OutputStream os = new FileOutputStream(f);
+        os.write("testdata".getBytes());
+        os.close();
+
+        String testBody = "<?xml version='1.0'?>\n<!DOCTYPE foo [" + " <!ENTITY test SYSTEM \"file:" + dname + "/" + fname + "\">"
+                + "]>\n<foo>&test;</foo>";
+        InputStream is = new ByteArrayInputStream(testBody.getBytes("UTF-8"));
+
+        try {
+            Document d = DomUtil.parseDocument(is);
+            Element root = d.getDocumentElement();
+            String text = DomUtil.getText(root);
+            fail("parsing this document should cause an exception, but the following external content was included: " + text);
+        } catch (Exception expected) {
+        }
+    }
+
+    public void testCustomEntityResolver() throws ParserConfigurationException, SAXException, IOException {
+
+        try {
+            DocumentBuilderFactory dbf = new DocumentBuilderFactory() {
+
+                DocumentBuilderFactory def = DocumentBuilderFactory.newInstance();
+
+                @Override
+                public void setFeature(String name, boolean value) throws ParserConfigurationException {
+                    def.setFeature(name, value);
+                }
+
+                @Override
+                public void setAttribute(String name, Object value) throws IllegalArgumentException {
+                    def.setAttribute(name, value);
+                }
+
+                @Override
+                public DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
+                    DocumentBuilder db = def.newDocumentBuilder();
+                    db.setEntityResolver(new EntityResolver() {
+                        @Override
+                        public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
+                            if ("foo:test".equals(systemId)) {
+                                return new InputSource(new ByteArrayInputStream("foo&bar".getBytes("UTF-8")));
+                            } else {
+                                return null;
+                            }
+                        }
+                    });
+                    return db;
+                }
+
+                @Override
+                public boolean getFeature(String name) throws ParserConfigurationException {
+                    return def.getFeature(name);
+                }
+
+                @Override
+                public Object getAttribute(String name) throws IllegalArgumentException {
+                    return def.getAttribute(name);
+                }
+            };
+
+            DomUtil.setBuilderFactory(dbf);
+            String testBody = "<?xml version='1.0'?>\n<!DOCTYPE foo [" + " <!ENTITY test SYSTEM \"foo:test\">"
+                    + "]>\n<foo>&test;</foo>";
+            InputStream is = new ByteArrayInputStream(testBody.getBytes("UTF-8"));
+
+            Document d = DomUtil.parseDocument(is);
+            Element root = d.getDocumentElement();
+            String text = DomUtil.getText(root);
+            assertEquals("custom entity resolver apparently not called", "foo&bar", text);
+        } finally {
+            DomUtil.setBuilderFactory(null);
+        }
+    }
+}
\ No newline at end of file
diff --git a/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/TestAll.java b/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/TestAll.java
index 1ca395a..f3ff354 100644
--- a/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/TestAll.java
+++ b/jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/xml/TestAll.java
@@ -33,6 +33,7 @@ public class TestAll extends TestCase {
         TestSuite suite = new TestSuite("org.apache.jackrabbit.webdav.xml tests");
 
         suite.addTestSuite(NamespaceTest.class);
+        suite.addTestSuite(ParserTest.class);
 
         return suite;
     }
diff --git a/pom.xml b/pom.xml
index 1ee0502..4052b61 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,7 +27,7 @@
   <parent>
     <groupId>org.apache.jackrabbit</groupId>
     <artifactId>jackrabbit-parent</artifactId>
-    <version>2.3.6</version>
+    <version>2.10.1</version>
     <relativePath>jackrabbit-parent/pom.xml</relativePath>
   </parent>
 
@@ -40,6 +40,8 @@
     <module>jackrabbit-api</module>
     <module>jackrabbit-jcr-commons</module>
     <module>jackrabbit-jcr-tests</module>
+    <module>jackrabbit-data</module>
+    <module>jackrabbit-aws-ext</module>
     <module>jackrabbit-core</module>
     <module>jackrabbit-webdav</module>
     <module>jackrabbit-jcr-server</module>
@@ -59,9 +61,9 @@
   </modules>
 
   <scm>
-    <connection>scm:svn:http://svn.apache.org/repos/asf/jackrabbit/tags/2.3.6</connection>
-    <developerConnection>scm:svn:https://svn.apache.org/repos/asf/jackrabbit/tags/2.3.6</developerConnection>
-    <url>http://svn.apache.org/viewvc/jackrabbit/tags/2.3.6</url>
+    <connection>scm:svn:http://svn.apache.org/repos/asf/jackrabbit/tags/jackrabbit-2.10.1</connection>
+    <developerConnection>scm:svn:https://svn.apache.org/repos/asf/jackrabbit/tags/jackrabbit-2.10.1</developerConnection>
+    <url>http://svn.apache.org/viewvc/jackrabbit/tags/jackrabbit-2.10.1</url>
   </scm>
 
   <build>
@@ -81,7 +83,9 @@
             <exclude>test/**/target/**</exclude>
             <exclude>test/**/.*/**</exclude>
             <exclude>test/**/*.log</exclude>
+            <exclude>test/performance/base/src/main/resources/deepTree.xml</exclude>
             <exclude>.git/**</exclude>
+            <exclude>.gitignore</exclude>
           </excludes>
         </configuration>
       </plugin>
@@ -161,11 +165,11 @@ Subject: [VOTE] Release Apache Jackrabbit ${project.version}
 
 A candidate for the Jackrabbit ${project.version} release is available at:
 
-    http://people.apache.org/~${username}/jackrabbit/${project.version}/
+    https://dist.apache.org/repos/dist/dev/jackrabbit/${project.version}/
 
 The release candidate is a zip archive of the sources in:
 
-    http://svn.apache.org/repos/asf/jackrabbit/tags/${project.version}/
+    https://svn.apache.org/repos/asf/jackrabbit/tags/${project.version}/
 
 The SHA1 checksum of the archive is ${checksum}.
 
@@ -175,7 +179,7 @@ A staged Maven repository is available for review at:
 
 The command for running automated checks against this release candidate is:
 
-    $ sh check-release.sh ${username} ${project.version} ${checksum}
+    $ sh check-release.sh ${project.version} ${checksum}
 
 Please vote on releasing this package as Apache Jackrabbit ${project.version}.
 The vote is open for the next 72 hours and passes if a majority of at
@@ -190,9 +194,12 @@ The release candidate has been prepared in:
 
     ${basedir}/target/${project.version}
 
-Please deploy it to people.apache.org like this:
+Please deploy it to https://dist.apache.org/repos/dist/dev/jackrabbit/:
 
-    scp -r ${basedir}/target/${project.version} people.apache.org:public_html/jackrabbit/
+    cd /path/to/jackrabbit-dev
+    mv ${basedir}/target/${project.version} ${project.version}
+    svn add ${project.version}
+    svn commit -m 'Apache Jackrabbit ${project.version} release candidate' ${project.version}
 
 A release vote template has been generated for you:
 
diff --git a/test/compatibility/README.txt b/test/compatibility/README.txt
index a80bf16..eb7cab1 100644
--- a/test/compatibility/README.txt
+++ b/test/compatibility/README.txt
@@ -1,7 +1,7 @@
-See org.apache.jackrabbit.core.integration.BackwardsCompatibilityTest
+See org.apache.jackrabbit.j2ee.BackwardsCompatibilityIT
 
-The repositories.zip file contains pre-generated Jackrabbit repositories
+The repository zip file contains pre-generated Jackrabbit repositories
 from past releases and configurations. If you make changes to the repository
-content or add new versions, you can update the repositories.zip file by
-running "mvn clean install" and using the resulting zip file from the
-target directory to replace repositories.zip.
+content or add new versions, you can update the repository zip file by
+running "mvn clean install" and using the result from the target directory
+to replace jackrabbit-webapp/src/test/resources/compatibility.zip.
diff --git a/test/compatibility/assembly.xml b/test/compatibility/assembly.xml
index d9bd356..ef19471 100644
--- a/test/compatibility/assembly.xml
+++ b/test/compatibility/assembly.xml
@@ -64,5 +64,13 @@
       <directory>create22/target/repository</directory>
       <outputDirectory></outputDirectory>
     </fileSet>
+    <fileSet>
+      <directory>create24/target/repository</directory>
+      <outputDirectory></outputDirectory>
+    </fileSet>
+    <fileSet>
+      <directory>create26/target/repository</directory>
+      <outputDirectory></outputDirectory>
+    </fileSet>
   </fileSets>
 </assembly>
diff --git a/test/compatibility/create24/pom.xml b/test/compatibility/create24/pom.xml
new file mode 100644
index 0000000..0db75ec
--- /dev/null
+++ b/test/compatibility/create24/pom.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+                             http://maven.apache.org/maven-v4_0_0.xsd ">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.jackrabbit</groupId>
+    <artifactId>jackrabbit-bc-parent</artifactId>
+    <version>SNAPSHOT</version>
+    <relativePath>../parent/pom.xml</relativePath>
+  </parent>
+
+  <artifactId>jackrabbit-bc-create24</artifactId>
+  <name>Jackrabbit 2.4 Repositories</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>jackrabbit-bc-base</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.jcr</groupId>
+      <artifactId>jcr</artifactId>
+      <version>2.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jackrabbit</groupId>
+      <artifactId>jackrabbit-core</artifactId>
+      <version>2.4.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-log4j12</artifactId>
+      <version>1.6.4</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
+
diff --git a/test/compatibility/create24/src/test/java/org/apache/jackrabbit/harness/compatibility/CreateRepositoryTest.java b/test/compatibility/create24/src/test/java/org/apache/jackrabbit/harness/compatibility/CreateRepositoryTest.java
new file mode 100644
index 0000000..781e74a
--- /dev/null
+++ b/test/compatibility/create24/src/test/java/org/apache/jackrabbit/harness/compatibility/CreateRepositoryTest.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.harness.compatibility;
+
+import org.testng.annotations.Test;
+
+public class CreateRepositoryTest extends AbstractRepositoryTest {
+
+    @Test
+    public void createRepositories() throws Exception {
+        doCreateRepositories("jackrabbit-2.4");
+    }
+
+}
diff --git a/test/compatibility/create26/pom.xml b/test/compatibility/create26/pom.xml
new file mode 100644
index 0000000..b5e15cf
--- /dev/null
+++ b/test/compatibility/create26/pom.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+                             http://maven.apache.org/maven-v4_0_0.xsd ">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.jackrabbit</groupId>
+    <artifactId>jackrabbit-bc-parent</artifactId>
+    <version>SNAPSHOT</version>
+    <relativePath>../parent/pom.xml</relativePath>
+  </parent>
+
+  <artifactId>jackrabbit-bc-create26</artifactId>
+  <name>Jackrabbit 2.6 Repositories</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>jackrabbit-bc-base</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.jcr</groupId>
+      <artifactId>jcr</artifactId>
+      <version>2.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jackrabbit</groupId>
+      <artifactId>jackrabbit-core</artifactId>
+      <version>2.6.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-log4j12</artifactId>
+      <version>1.6.4</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
+
diff --git a/test/compatibility/create26/src/test/java/org/apache/jackrabbit/harness/compatibility/CreateRepositoryTest.java b/test/compatibility/create26/src/test/java/org/apache/jackrabbit/harness/compatibility/CreateRepositoryTest.java
new file mode 100644
index 0000000..3e5d8cd
--- /dev/null
+++ b/test/compatibility/create26/src/test/java/org/apache/jackrabbit/harness/compatibility/CreateRepositoryTest.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.harness.compatibility;
+
+import org.testng.annotations.Test;
+
+public class CreateRepositoryTest extends AbstractRepositoryTest {
+
+    @Test
+    public void createRepositories() throws Exception {
+        doCreateRepositories("jackrabbit-2.6");
+    }
+
+}
diff --git a/test/compatibility/pom.xml b/test/compatibility/pom.xml
index 953088e..7bf4352 100644
--- a/test/compatibility/pom.xml
+++ b/test/compatibility/pom.xml
@@ -51,6 +51,8 @@
     <module>create20</module>
     <module>create21</module>
     <module>create22</module>
+    <module>create24</module>
+    <module>create26</module>
   </modules>
 
   <build>
diff --git a/test/performance/base/pom.xml b/test/performance/base/pom.xml
index e585087..193e874 100644
--- a/test/performance/base/pom.xml
+++ b/test/performance/base/pom.xml
@@ -37,7 +37,7 @@
     <dependency>
       <groupId>javax.jcr</groupId>
       <artifactId>jcr</artifactId>
-      <version>1.0</version>
+      <version>2.0</version>
     </dependency>
     <dependency>
       <groupId>org.apache.commons</groupId>
diff --git a/test/performance/base/src/main/java/org/apache/jackrabbit/performance/AbstractDeepTreeTest.java b/test/performance/base/src/main/java/org/apache/jackrabbit/performance/AbstractDeepTreeTest.java
new file mode 100644
index 0000000..b670492
--- /dev/null
+++ b/test/performance/base/src/main/java/org/apache/jackrabbit/performance/AbstractDeepTreeTest.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.performance;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.Item;
+import javax.jcr.ItemVisitor;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.util.TraversingItemVisitor;
+
+public abstract class AbstractDeepTreeTest extends AbstractTest {
+
+    protected Session adminSession;
+    protected Node testRoot;
+
+    protected List<String> allPaths;
+
+    @Override
+    protected void beforeSuite() throws Exception {
+        adminSession = getRepository().login(getCredentials());
+        String name = getClass().getSimpleName();
+        Node rn = adminSession.getRootNode();
+
+        if (!rn.hasNode(name)) {
+            testRoot = adminSession.getRootNode().addNode(name, "nt:unstructured");
+            InputStream in = getClass().getClassLoader().getResourceAsStream("deepTree.xml");
+            adminSession.importXML(testRoot.getPath(), in, ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW);
+            adminSession.save();
+        } else {
+            testRoot = rn.getNode(name);
+        }
+
+        final List<String> paths = new ArrayList<String>();
+        ItemVisitor v = new TraversingItemVisitor.Default() {
+            @Override
+            protected void entering(Node node, int i) throws RepositoryException {
+                paths.add(node.getPath());
+                super.entering(node, i);
+            }
+            @Override
+            protected void entering(Property prop, int i) throws RepositoryException {
+                paths.add(prop.getPath());
+                super.entering(prop, i);
+            }
+        };
+        v.visit(testRoot);
+        allPaths = paths;
+
+        System.out.println("All paths: " + allPaths.size());
+    }
+
+    @Override
+    protected void afterSuite() throws Exception {
+        testRoot.remove();
+        adminSession.save();
+        adminSession.logout();
+    }
+
+    protected static void randomRead(Session testSession, List<String> allPaths, int cnt, boolean doReport) throws RepositoryException {
+        int nodeCnt = 0;
+        int propertyCnt = 0;
+        int noAccess = 0;
+        int size = allPaths.size();
+        long start = System.currentTimeMillis();
+        for (int i = 0; i < cnt; i++) {
+            double rand = size * Math.random();
+            int index = (int) Math.floor(rand);
+            String path = allPaths.get(index);
+            if (testSession.itemExists(path)) {
+                Item item = testSession.getItem(path);
+                if (item.isNode()) {
+                    nodeCnt++;
+                } else {
+                    propertyCnt++;
+                }
+            } else {
+                noAccess++;
+            }
+        }
+        long end = System.currentTimeMillis();
+        if (doReport) {
+            System.out.println("Reading " + (cnt-noAccess) + " (Nodes: "+ nodeCnt +"; Properties: "+propertyCnt+") completed in " + (end - start));
+        }
+    }
+}
diff --git a/test/performance/base/src/main/java/org/apache/jackrabbit/performance/ConcurrentReadAccessControlledTreeTest.java b/test/performance/base/src/main/java/org/apache/jackrabbit/performance/ConcurrentReadAccessControlledTreeTest.java
new file mode 100644
index 0000000..1bd1eab
--- /dev/null
+++ b/test/performance/base/src/main/java/org/apache/jackrabbit/performance/ConcurrentReadAccessControlledTreeTest.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.performance;
+
+import javax.jcr.ItemVisitor;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.security.AccessControlList;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.AccessControlPolicyIterator;
+import javax.jcr.security.Privilege;
+import javax.jcr.util.TraversingItemVisitor;
+
+import org.apache.jackrabbit.core.security.principal.EveryonePrincipal;
+
+/**
+ * Concurrently reads random items from the deep tree where every 10th node is
+ * access controlled.
+ */
+public class ConcurrentReadAccessControlledTreeTest extends AbstractDeepTreeTest {
+
+    private int bgReaders = 20;
+    private int cnt = 10000;
+
+    @Override
+    protected void beforeSuite() throws Exception {
+        super.beforeSuite();
+
+        ItemVisitor visitor = new TraversingItemVisitor.Default() {
+            int counter = 0;
+            @Override
+            protected void entering(Node node, int level) throws RepositoryException {
+                if (++counter == 10) {
+                    addPolicy(node);
+                    counter = 0;
+                }
+                super.entering(node, level);
+            }
+
+            private void addPolicy(Node node) throws RepositoryException {
+                AccessControlManager acMgr = node.getSession().getAccessControlManager();
+                String path = node.getPath();
+                AccessControlPolicyIterator acIterator = acMgr.getApplicablePolicies(path);
+                if (acIterator.hasNext()) {
+                    AccessControlPolicy policy = acIterator.nextAccessControlPolicy();
+                    if (policy instanceof AccessControlList) {
+                        AccessControlList acl = (AccessControlList) policy;
+                        Privilege[] privileges = new Privilege[] {
+                                acMgr.privilegeFromName(Privilege.JCR_READ),
+                                acMgr.privilegeFromName(Privilege.JCR_READ_ACCESS_CONTROL)
+                        };
+                        if (acl.addAccessControlEntry(EveryonePrincipal.getInstance(), privileges)) {
+                            acMgr.setPolicy(path, acl);
+                            node.getSession().save();
+                        }
+                    }
+                }
+            }
+        };
+
+        visitor.visit(testRoot);
+
+        for (int i = 0; i < bgReaders; i++) {
+            addBackgroundJob(new RandomRead(loginReader(), false));
+        }
+    }
+
+    @Override
+    protected void runTest() throws Exception {
+        Session testSession = getRepository().login();
+        RandomRead randomRead = new RandomRead(testSession, true);
+        randomRead.run();
+        testSession.logout();
+    }
+
+    private class RandomRead implements Runnable {
+
+        private final Session testSession;
+        private final boolean doReport;
+
+        private RandomRead(Session testSession, boolean doReport) {
+            this.testSession = testSession;
+            this.doReport = doReport;
+        }
+        public void run() {
+            try {
+                randomRead(testSession, allPaths, cnt, doReport);
+            } catch (RepositoryException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/performance/base/src/main/java/org/apache/jackrabbit/performance/ConcurrentReadDeepTreeTest.java b/test/performance/base/src/main/java/org/apache/jackrabbit/performance/ConcurrentReadDeepTreeTest.java
new file mode 100644
index 0000000..d2db4a0
--- /dev/null
+++ b/test/performance/base/src/main/java/org/apache/jackrabbit/performance/ConcurrentReadDeepTreeTest.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.performance;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+/**
+ * Concurrently reads random items from the deep tree.
+ */
+public class ConcurrentReadDeepTreeTest extends AbstractDeepTreeTest {
+
+    private int bgReaders = 50;
+    private int cnt = 10000;
+
+    @Override
+    protected void beforeSuite() throws Exception {
+        super.beforeSuite();
+
+        for (int i = 0; i < bgReaders; i++) {
+            addBackgroundJob(new RandomRead(loginReader()));
+        }
+    }
+
+    @Override
+    protected void runTest() throws Exception {
+        Session testSession = getRepository().login();
+        RandomRead randomRead = new RandomRead(testSession);
+        randomRead.run();
+        testSession.logout();
+    }
+
+    private class RandomRead implements Runnable {
+
+        private final Session testSession;
+
+        private RandomRead(Session testSession) {
+            this.testSession = testSession;
+        }
+
+        public void run() {
+            try {
+                randomRead(testSession, allPaths, cnt, true);
+            } catch (RepositoryException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/test/performance/base/src/main/java/org/apache/jackrabbit/performance/ReadDeepTreeTest.java b/test/performance/base/src/main/java/org/apache/jackrabbit/performance/ReadDeepTreeTest.java
new file mode 100644
index 0000000..d0a932d
--- /dev/null
+++ b/test/performance/base/src/main/java/org/apache/jackrabbit/performance/ReadDeepTreeTest.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.performance;
+
+import javax.jcr.Session;
+
+/**
+ * Randomly read 100000 items from the deep tree.
+ */
+public class ReadDeepTreeTest extends AbstractDeepTreeTest {
+
+    private Session testSession;
+    private int cnt = 100000;
+
+    @Override
+    protected void beforeSuite() throws Exception {
+        super.beforeSuite();
+
+        testSession = getRepository().login();
+    }
+
+    @Override
+    protected void runTest() throws Exception {
+        randomRead(testSession, allPaths, cnt, true);
+    }
+
+    @Override
+    protected void afterSuite() throws Exception {
+        testSession.logout();
+    }
+}
\ No newline at end of file
diff --git a/test/performance/base/src/main/resources/deepTree.xml b/test/performance/base/src/main/resources/deepTree.xml
new file mode 100644
index 0000000..ac071fe
--- /dev/null
+++ b/test/performance/base/src/main/resources/deepTree.xml
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><sv:node sv:name="deepTree" xmlns:sv="http://www.jcp.org/jcr/sv/1.0" xmlns:rep="internal" xmlns:jcr="http://www.jcp.org/jcr/1.0"><sv:property sv:name="jcr:primaryType" sv:type="Name"><sv:value>nt:folder</sv:value></sv:property><sv:property sv:name="jcr:created" sv:type="Date"><sv:value>2012-06-14T12:09:22.634+02:00</sv:value></sv:property><sv:property sv:name="jcr:createdBy" sv:type="String"><sv:value>admin</sv:value></sv:property><sv:node sv:name="f [...]
\ No newline at end of file
diff --git a/test/performance/jackrabbit26/pom.xml b/test/performance/jackrabbit26/pom.xml
new file mode 100644
index 0000000..2bb79ff
--- /dev/null
+++ b/test/performance/jackrabbit26/pom.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+                             http://maven.apache.org/maven-v4_0_0.xsd ">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.jackrabbit</groupId>
+    <artifactId>jackrabbit-perf-parent</artifactId>
+    <version>1-SNAPSHOT</version>
+    <relativePath>../parent/pom.xml</relativePath>
+  </parent>
+
+  <artifactId>jackrabbit-perf-jackrabbit26</artifactId>
+  <name>Jackrabbit 2.6 Performance Test</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.jackrabbit</groupId>
+      <artifactId>jackrabbit-perf-base</artifactId>
+      <version>1-SNAPSHOT</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.jcr</groupId>
+      <artifactId>jcr</artifactId>
+      <version>2.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.jackrabbit</groupId>
+      <artifactId>jackrabbit-core</artifactId>
+      <version>2.6-SNAPSHOT</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
+
diff --git a/test/performance/jackrabbit26/src/test/java/org/apache/jackrabbit/performance/PerformanceTest.java b/test/performance/jackrabbit26/src/test/java/org/apache/jackrabbit/performance/PerformanceTest.java
new file mode 100644
index 0000000..8f4fd77
--- /dev/null
+++ b/test/performance/jackrabbit26/src/test/java/org/apache/jackrabbit/performance/PerformanceTest.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.performance;
+
+import org.apache.jackrabbit.core.query.lucene.join.QueryEngine;
+import org.testng.annotations.Test;
+
+public class PerformanceTest extends AbstractPerformanceTest {
+
+    @Test
+    public void testPerformance() throws Exception {
+        testPerformance("2.6");
+
+        System.setProperty(QueryEngine.NATIVE_SORT_SYSTEM_PROPERTY, "true");
+        testPerformance("2.6-expSort", getDefaultConfig());
+        System.setProperty(QueryEngine.NATIVE_SORT_SYSTEM_PROPERTY, "false");
+    }
+}
\ No newline at end of file
diff --git a/test/performance/jackrabbit26/src/test/resources/btree-usermanager-repository.xml b/test/performance/jackrabbit26/src/test/resources/btree-usermanager-repository.xml
new file mode 100644
index 0000000..34bdbb2
--- /dev/null
+++ b/test/performance/jackrabbit26/src/test/resources/btree-usermanager-repository.xml
@@ -0,0 +1,159 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
+                            "http://jackrabbit.apache.org/dtd/repository-1.6.dtd">
+<!-- Example Repository Configuration File -->
+<Repository>
+    <!--
+        virtual file system where the repository stores global state
+        (e.g. registered namespaces, custom node types, etc.)
+    -->
+    <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+        <param name="path" value="${rep.home}/repository"/>
+    </FileSystem>
+
+    <!--
+        data store configuration
+    -->
+    <DataStore class="org.apache.jackrabbit.core.data.FileDataStore"/>
+    <!--
+        sample database data store configuration
+        <DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
+            <param name="url" value="jdbc:h2:~/test"/>
+            <param name="user" value="sa"/>
+            <param name="password" value="sa"/>
+        </DataStore>
+    -->
+    
+    <!--
+        repository lock mechanism configuration
+    <RepositoryLockMechanism class="org.apache.jackrabbit.core.util.CooperativeFileLock"/>
+    -->
+
+    <!--
+        security configuration
+    -->
+    <Security appName="Jackrabbit">
+        <!--
+            security manager:
+            class: FQN of class implementing the JackrabbitSecurityManager interface
+        -->
+        <SecurityManager class="org.apache.jackrabbit.core.DefaultSecurityManager" workspaceName="security">
+            <!-- <param name="config" value="${rep.home}/security.xml"/> -->
+            <UserManager class="org.apache.jackrabbit.core.security.user.UserManagerImpl">
+                <param name="groupMembershipSplitSize" value="25" />
+            </UserManager>
+        </SecurityManager>
+
+        <!--
+            access manager:
+            class: FQN of class implementing the AccessManager interface
+        -->
+        <AccessManager class="org.apache.jackrabbit.core.security.DefaultAccessManager">
+            <!-- <param name="config" value="${rep.home}/access.xml"/> -->
+        </AccessManager>
+
+        <LoginModule class="org.apache.jackrabbit.core.security.authentication.DefaultLoginModule">
+           <!-- 
+              anonymous user name ('anonymous' is the default value)
+            -->
+           <param name="anonymousId" value="anonymous"/>
+           <!--
+              administrator user id (default value if param is missing is 'admin')
+            -->
+           <param name="adminId" value="admin"/>
+           <!--
+              optional parameter 'principalProvider'.
+              the value refers to the class name of the PrincipalProvider implementation.
+           -->
+           <!-- <param name="principalProvider" value="..."/> -->
+        </LoginModule>
+    </Security>
+
+    <!--
+        location of workspaces root directory and name of default workspace
+    -->
+    <Workspaces rootPath="${rep.home}/workspaces" defaultWorkspace="default" maxIdleTime="2"/>
+    <!--
+        workspace configuration template:
+        used to create the initial workspace if there's no workspace yet
+    -->
+    <Workspace name="${wsp.name}">
+        <!--
+            virtual file system of the workspace:
+            class: FQN of class implementing the FileSystem interface
+        -->
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${wsp.home}"/>
+        </FileSystem>
+        <!--
+            persistence manager of the workspace:
+            class: FQN of class implementing the PersistenceManager interface
+        -->
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+          <param name="url" value="jdbc:derby:${wsp.home}/db;create=true"/>
+          <param name="schemaObjectPrefix" value="${wsp.name}_"/>
+        </PersistenceManager>
+        <!--
+            Search index and the file system it uses.
+            class: FQN of class implementing the QueryHandler interface
+        -->
+        <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+            <param name="path" value="${wsp.home}/index"/>
+        </SearchIndex>
+    </Workspace>
+
+    <!--
+        Configures the versioning
+    -->
+    <Versioning rootPath="${rep.home}/version">
+        <!--
+            Configures the filesystem to use for versioning for the respective
+            persistence manager
+        -->
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${rep.home}/version" />
+        </FileSystem>
+
+        <!--
+            Configures the persistence manager to be used for persisting version state.
+            Please note that the current versioning implementation is based on
+            a 'normal' persistence manager, but this could change in future
+            implementations.
+        -->
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+          <param name="url" value="jdbc:derby:${rep.home}/version/db;create=true"/>
+          <param name="schemaObjectPrefix" value="version_"/>
+        </PersistenceManager>
+    </Versioning>
+
+    <!--
+        Search index for content that is shared repository wide
+        (/jcr:system tree, contains mainly versions)
+    -->
+    <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+        <param name="path" value="${rep.home}/repository/index"/>
+    </SearchIndex>
+    
+    <!--
+        Run with a cluster journal
+    -->
+    <Cluster id="node1">
+        <Journal class="org.apache.jackrabbit.core.journal.MemoryJournal"/>
+    </Cluster>
+</Repository>
diff --git a/test/performance/jackrabbit26/src/test/resources/default-usermanager-repository.xml b/test/performance/jackrabbit26/src/test/resources/default-usermanager-repository.xml
new file mode 100644
index 0000000..c3e80fa
--- /dev/null
+++ b/test/performance/jackrabbit26/src/test/resources/default-usermanager-repository.xml
@@ -0,0 +1,156 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
+                            "http://jackrabbit.apache.org/dtd/repository-1.6.dtd">
+<!-- Example Repository Configuration File -->
+<Repository>
+    <!--
+        virtual file system where the repository stores global state
+        (e.g. registered namespaces, custom node types, etc.)
+    -->
+    <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+        <param name="path" value="${rep.home}/repository"/>
+    </FileSystem>
+
+    <!--
+        data store configuration
+    -->
+    <DataStore class="org.apache.jackrabbit.core.data.FileDataStore"/>
+    <!--
+        sample database data store configuration
+        <DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
+            <param name="url" value="jdbc:h2:~/test"/>
+            <param name="user" value="sa"/>
+            <param name="password" value="sa"/>
+        </DataStore>
+    -->
+    
+    <!--
+        repository lock mechanism configuration
+    <RepositoryLockMechanism class="org.apache.jackrabbit.core.util.CooperativeFileLock"/>
+    -->
+
+    <!--
+        security configuration
+    -->
+    <Security appName="Jackrabbit">
+        <!--
+            security manager:
+            class: FQN of class implementing the JackrabbitSecurityManager interface
+        -->
+        <SecurityManager class="org.apache.jackrabbit.core.DefaultSecurityManager" workspaceName="security">
+            <!-- <param name="config" value="${rep.home}/security.xml"/> -->
+        </SecurityManager>
+
+        <!--
+            access manager:
+            class: FQN of class implementing the AccessManager interface
+        -->
+        <AccessManager class="org.apache.jackrabbit.core.security.DefaultAccessManager">
+            <!-- <param name="config" value="${rep.home}/access.xml"/> -->
+        </AccessManager>
+
+        <LoginModule class="org.apache.jackrabbit.core.security.authentication.DefaultLoginModule">
+           <!-- 
+              anonymous user name ('anonymous' is the default value)
+            -->
+           <param name="anonymousId" value="anonymous"/>
+           <!--
+              administrator user id (default value if param is missing is 'admin')
+            -->
+           <param name="adminId" value="admin"/>
+           <!--
+              optional parameter 'principalProvider'.
+              the value refers to the class name of the PrincipalProvider implementation.
+           -->
+           <!-- <param name="principalProvider" value="..."/> -->
+        </LoginModule>
+    </Security>
+
+    <!--
+        location of workspaces root directory and name of default workspace
+    -->
+    <Workspaces rootPath="${rep.home}/workspaces" defaultWorkspace="default" maxIdleTime="2"/>
+    <!--
+        workspace configuration template:
+        used to create the initial workspace if there's no workspace yet
+    -->
+    <Workspace name="${wsp.name}">
+        <!--
+            virtual file system of the workspace:
+            class: FQN of class implementing the FileSystem interface
+        -->
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${wsp.home}"/>
+        </FileSystem>
+        <!--
+            persistence manager of the workspace:
+            class: FQN of class implementing the PersistenceManager interface
+        -->
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+          <param name="url" value="jdbc:derby:${wsp.home}/db;create=true"/>
+          <param name="schemaObjectPrefix" value="${wsp.name}_"/>
+        </PersistenceManager>
+        <!--
+            Search index and the file system it uses.
+            class: FQN of class implementing the QueryHandler interface
+        -->
+        <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+            <param name="path" value="${wsp.home}/index"/>
+        </SearchIndex>
+    </Workspace>
+
+    <!--
+        Configures the versioning
+    -->
+    <Versioning rootPath="${rep.home}/version">
+        <!--
+            Configures the filesystem to use for versioning for the respective
+            persistence manager
+        -->
+        <FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem">
+            <param name="path" value="${rep.home}/version" />
+        </FileSystem>
+
+        <!--
+            Configures the persistence manager to be used for persisting version state.
+            Please note that the current versioning implementation is based on
+            a 'normal' persistence manager, but this could change in future
+            implementations.
+        -->
+        <PersistenceManager class="org.apache.jackrabbit.core.persistence.pool.DerbyPersistenceManager">
+          <param name="url" value="jdbc:derby:${rep.home}/version/db;create=true"/>
+          <param name="schemaObjectPrefix" value="version_"/>
+        </PersistenceManager>
+    </Versioning>
+
+    <!--
+        Search index for content that is shared repository wide
+        (/jcr:system tree, contains mainly versions)
+    -->
+    <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+        <param name="path" value="${rep.home}/repository/index"/>
+    </SearchIndex>
+    
+    <!--
+        Run with a cluster journal
+    -->
+    <Cluster id="node1">
+        <Journal class="org.apache.jackrabbit.core.journal.MemoryJournal"/>
+    </Cluster>
+</Repository>
diff --git a/test/performance/pom.xml b/test/performance/pom.xml
index b9616e2..4a0330d 100644
--- a/test/performance/pom.xml
+++ b/test/performance/pom.xml
@@ -41,18 +41,19 @@
   <modules>
     <module>parent</module>
     <module>base</module>
-    <module>jackrabbit10</module>
+    <!--module>jackrabbit10</module>
     <module>jackrabbit11</module>
     <module>jackrabbit12</module>
     <module>jackrabbit13</module>
     <module>jackrabbit14</module>
     <module>jackrabbit15</module>
-    <module>jackrabbit16</module>
+    <module>jackrabbit16</module-->
     <module>jackrabbit20</module>
     <module>jackrabbit21</module>
     <module>jackrabbit22</module>
     <module>jackrabbit23</module>
     <module>jackrabbit24</module>
+    <module>jackrabbit26</module>
   </modules>
 
 </project>

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/jackrabbit.git



More information about the pkg-java-commits mailing list